Thursday, August 17, 2017

How to test whether Dexter is working properly with Star Rider

Star Rider is an overly complicated system and has many points of potential failure.  How can one test whether Dexter is to blame for problems with one's Star Rider game?

Fortunately, Williams included some nice built-in diagnostic tools in Star Rider.  To access it, press the ADVANCE button during the red 'moire' test (shown below), or any point thereafter during the attract mode.  Make sure the AUTO-UP/MANUAL-DOWN switch is in the DOWN position when doing this.  Both of these controls are supposedly inside of the coin door on a real cabinet but I've never had access to a real cabinet, so I am only going off what I've heard.

Press ADVANCE once you see this screen to get into the diagnostics screens.

The first test that comes up is the ROM test.  The board on the left is the ROM board that sits on top of the VGG board.  If all of the ROMs are failing, check the ribbon cable going from the ROM board to the VGG board.  Also check the two PROMs on the VGG board as they need to be good in order for these ROMs to be correctly read by the CPU.

The board on the right is the CPU board.  If any of these 5 tests fail, do not even bother continuing as nothing else is likely to work if you have CPU ROM read problems.

There are several more tests before the disc test: RAM test (uses rug pattern, failures here may be non-fatal or fatal depending on whether it's bad video RAM or CPU RAM), CMOS test (tests battery backed up RAM, failures here may be non-fatal), sound test (a missing or defective sound board will cause this test to not allow you to advance to the next test without spamming the ADVANCE button for about 30 seconds), and input test (steering mechanism, brakes, turbo, throttle, etc).

None of these aforementioned tests will tell you anything about Dexter so examine them at your leisure.

After the input test, hold down advance to get to the DISC TEST.  Make sure the AUTO-UP/MANUAL-DOWN switch is in MANUAL mode.

The four tests on the DISC TEST screen are controlled using the AUTO-UP/MANUAL-DOWN switch.  When in manual mode, the same test will execute over and over again.  When in auto mode, each test will execute in sequence one time.  I prefer a combination of the two: going into auto mode just long enough to advance to the next test, then switching back to manual mode.

Reset response test: This tests the communication from the VGG board to the PIF board.  It has nothing to d with whether Dexter is operational.  If this test fails, your game will not work period because it will not be able to talk to Dexter.  There are many points of failure that could cause this test to fail, but in my experience, a bad or unreliable PIF ROM is the #1 culprit.  The next thing I would check is the cable going from the PIF board to the VGG board.  I have yet to see a bad PIA6821.  Again, if this test fails, it's I/O from the VGG board to the PIF board, and so Dexter is not the cause of this problem.

The walking bit test is similar to the Reset Response test.  It tests communication from the VGG board to the PIF board.  It tests each of the 8 data bits one by one to make sure each bit is working properly.  If this test fails, it's a problem somewhere from the CPU/VGG to the PIF board, not Dexter.

The third test is the SEARCH TO test.  This test does test Dexter's ability to receive and execute search commands from the PIF board.  This test _does_ test whether Dexter is working properly.  If this test occasionally fails, it may be because you have not performed my PIF ROM modification yet.  Other reasons for this test failing are the VGG board not being able to correctly decode picture numbers from the video stream that Dexter generates, but this is just theoretical as I haven't actually seen a VGG board with this defect (yet).

Last but not least is the STEP DISC test.  This test checks to see whether the PIF board can run the laserdisc player in a variable speed manner, which is what the game needs to do in order to make your 'motorcycle' accelerate.  I've never seen this test fail if the SEARCH TO test is passing.  If it is failing but SEARCH TO is passing, then I would suspect that it's not Dexter's fault but instead a hardware defect on the VGG board or PIF board somewhere.  Like I said, there are many things that can fail to cause the whole system to break.




Whether your motorcycle gives hope to the helpless is in your hands... good luck.  Oh wait, wrong game!

Tuesday, August 15, 2017

Star Rider now working in Dexter (with a ROM mod) !!

TLDR: Star Rider will work with Dexter as it is today if one is willing to modify the Star Rider PIF ROM slightly.






The Details:

My goal with Dexter is to never make users have to modify their hardware.  I want Dexter to be a drop-in replacement.  However, I don't think I am going to be able to do it in this case without a new revision of the Dexter hardware.  Even then, this modification may also be a wise choice for people who may be using a real PR-8210A laserdisc player and/or arcade operators who don't have time to restart games which are too fussy and crash easily.

Inside the PIF ROM, here is the function that performs a laserdisc search:

ROM:F69B PR8210A_SEARCH:                         ; CODE XREF: ROM:FCBA P
ROM:F69B                                         ; DATA XREF: ROM:F048 o ...
ROM:F69B                 pshs    cc,a,b,x
ROM:F69D                 lda     #3
ROM:F69F                 sta     LastCallbackCmd ; Last callback we executed (only a handful keep track here).
ROM:F69F                                         ; FF: IRQ should do timer test, nothing else
ROM:F69F                                         ; 1: PLAY was called (f5af)
ROM:F69F                                         ; 3: SEARCH callback was called, either by cmd in 0x300 or from FIRQ command 03
ROM:F69F                                         ; 4: jump trigger count does not get cleared after jump trigger is sent (FIRQ cmd 4 received)
ROM:F69F                                         ; 18: No cmd executed yet (or NOP) - IRQ jump triggers disabled, even when on IRQ count 8 (see FD2D)
ROM:F69F                                         ; 1B: disc is in default paused state after IRQ test is finished, jump triggers are active (f564)
ROM:F6A1                 lda     PIAB_PolicyActive ; Holds byte that should be output to PR-8210A PIA port B whenever we aren't in the middle of an operation
ROM:F6A3                 sta     PIA_B_DATA
ROM:F6A6                 orcc    #$10            ; disable IRQ'
ROM:F6A8                 clra
ROM:F6A9                 clrb
ROM:F6AA                 std     SlowTimer       ; This appears to be a timer that increases very slowly (or not at all)
ROM:F6AA                                         ; Can get increased by IRQ
ROM:F6AA                                         ; written by FD68
ROM:F6AC                 clr     UnknownSlowTimerHelper ; gets a variation of what was stored in $43
ROM:F6AE                 std     Cmd04MysteryWord ; may get modified by ChangePlaybackDirection function (F771)
ROM:F6AE                                         ; may have something to do with playback speed
ROM:F6AE                                         ; If high bit is set, playback direction is reverse, otherwise it's forward.
ROM:F6B0                 clr     HowManyJmpTriggersToSend ; This value determines how many jump triggers the IRQ will send at once.
ROM:F6B0                                         ; 0: none
ROM:F6B0                                         ; 1: one
ROM:F6B0                                         ; 2: two
ROM:F6B0                                         ; 3: three
ROM:F6B0                                         ; 4: four
ROM:F6B0                                         ; (no more than 4 is supported)
ROM:F6B2                 clr     HowManyJmpTriggersToSendSrc ; holds jmp trigger count (F78C)
ROM:F6B4                 clr     IsDiscNotPaused ; will be non-zero if PLAY or REJECT command was last issued,
ROM:F6B4                                         ; will be 0 if the STEP FWD/REV (pause) command was last issued.
ROM:F6B6                 ldx     #SearchArrayEnd ; start at memory location 62 (and work backward)
ROM:F6B9                 clr     ,x              ; put 00 terminator at the end so send loop knows when to stop
ROM:F6BB                 lda     #$E8 ; 'F'      ; PR-8210A search command
ROM:F6BD                 sta     ,-x             ; 61 = E8, 60 = E8, 5F = E8
ROM:F6BF                 sta     ,-x
ROM:F6C1                 sta     ,-x
ROM:F6C3                 ldb     #5              ; may indicate 5 digits
ROM:F6C5                 pshs    b
ROM:F6C7                 ldd     [Pointer]       ; load the frame number (unsigned binary format)
ROM:F6CB 
ROM:F6CB DigitLoop:                              ; CODE XREF: PR8210A_SEARCH+35 j
ROM:F6CB                 jsr     Pr8210ALoadLeastSigDecimalDigit ; This call computes the least significant frame number digit (in decimal format),
ROM:F6CB                                         ; converts it to the PR-8210A expected format, then loads it into X-1 3 times.
ROM:F6CB                                         ; It also loads a filler byte (0x80).
ROM:F6CB                                         ; X will be equal to where to put the digit + 1
ROM:F6CB                                         ; the frame number will be in D
ROM:F6CE                 dec     ,s              ; the 'b' that we pushed on earlier, subtract it
ROM:F6D0                 bgt     DigitLoop
ROM:F6D2                 leas    1,s             ; pop off digit loop counter
ROM:F6D4                 lda     #$AC ; '¼'      ; PR-8210A FRAME DISPLAY command
ROM:F6D6                 sta     ,-x
ROM:F6D8                 sta     ,-x
ROM:F6DA                 sta     ,-x
ROM:F6DC                 lda     #$E8 ; 'F'      ; PR-8210A SEARCH command
ROM:F6DE                 sta     ,-x
ROM:F6E0                 sta     ,-x
ROM:F6E2                 sta     ,-x
ROM:F6E4                 jsr     Sleep50Ms       ; this delays for 49,993 cycles (from $FA16 until the last RTS has finished)
ROM:F6E4                                         ; So about 50 milliseconds
ROM:F6E7                 lda     ,x+
ROM:F6E9 
ROM:F6E9 WhileCmdArrayNotAllSent:                ; CODE XREF: PR8210A_SEARCH+56 j
ROM:F6E9                 jsr     Sleep10Ms       ; this method stalls for 10,004 cycles (from the JSR $FA21 until after RTS has completed)
ROM:F6E9                                         ; So about 10 milliseconds
ROM:F6EC                 jsr     SendPR8210ACmdNoFirq ; Sends two leading 0 bits to remote control line,
ROM:F6EC                                         ; then sends 8 bits stored in A, starting with
ROM:F6EC                                         ; bit 7 and shifting left.
ROM:F6EC                                         ; So for example, the play command would be 0xD0
ROM:F6EC                                         ; (1101 0000)
ROM:F6EC                                         ; FIRQ' is always suppressed, IRQ' may or may not be allowed
ROM:F6EF                 lda     ,x+
ROM:F6F1                 bne     WhileCmdArrayNotAllSent ; if we haven't hit the end (memory location 62) keep going
ROM:F6F3                 jsr     WaitForStandByToStopBlinking ; This waits for the STAND BY line to stop blinking.
ROM:F6F3                                         ; (it will also exit if the STAND BY line is held low)
ROM:F6F3                                         ; If it does not go low initially after about 450 milliseconds, the game will error out with code 0x86.
ROM:F6F3                                         ; 
ROM:F6F6                 ldx     Pointer         ; appears to hold pointer to frame number
ROM:F6F8                 leax    2,x             ; Advance to the next pointer in the FRAME NUMBER lookup table.
ROM:F6F8                                         ; This only seems to apply when SEARCH is called via (currently unknown) callback method.
ROM:F6F8                                         ; If it is called from FIRQ cmd, any change to this pointer gets reverted when this function returns.
ROM:F6FA                 stx     Pointer         ; appears to hold pointer to frame number
ROM:F6FC                 lda     ,s
ROM:F6FE                 tfr     a, cc
ROM:F700                 jsr     SyncIrqCounterToVsyncAndField ; Syncs up IRQ counter with vsync/field
ROM:F703                 andcc   #$EF ; 'n'      ; allow IRQ
ROM:F705                 jsr     DisableLeftAndRightAudio ; Disables left/right audio
ROM:F708                 jsr     PR8210A_PLAY_FIRQ_OK
ROM:F70B                 jsr     PrepareDiscPauseViaJmpTrig ; Sets default behavior to pause the disc using 1 jump trigger (in reverse direction) per track
ROM:F70E                 lda     #1
ROM:F710 
ROM:F710 WaitUntilIrqCounterIs1:                 ; CODE XREF: PR8210A_SEARCH+77 j
ROM:F710                 cmpa    IRQCounter      ; Counts how many times IRQ handler has been called.
ROM:F710                                         ; When this value is 8, the IRQ handler may issue JUMP TRIGGERS.
ROM:F710                                         ; This gets called every 4 MS approximately, so the JUMP TRIGGERS will occur
ROM:F710                                         ;  once per frame if they happen at all.
ROM:F710                                         ; 
ROM:F712                 bne     WaitUntilIrqCounterIs1
ROM:F714                 puls    x,b,a,cc
ROM:F716                 rts
ROM:F716 ; End of function PR8210A_SEARCH

Notice at $F6A6, IRQs are disabled, and then they are re-enabled at $F703, right after the internal Irq Counter has synced up with vsync and the current field.  The reason that this needs to happen (IMO) is because during searches, there is no sync, and the game relies on being in sync in order to send jump triggers at the right time.  So it makes sense that after a search, the game would need to go through a period of re-sync.

However, this is where things get inconsistent.

ROM:F880 SyncIrqCountToVsyncEnd:                 ; CODE XREF: SyncIrqCounterToVsyncAndField+33 P
ROM:F880                                         ; SyncIrqCounterToVsyncAndField+54 P
ROM:F880                 pshs    cc,a,b,x
ROM:F882                 andcc   #$EF ; 'n'      ; enable IRQ
ROM:F884                 jsr     Wait20msForVsync ; returns carry clear if vsync can be detected within ~20ms or carry set if timeout happens first
ROM:F887                 lbcs    PrepareToReset  ; prepares state to start the ROM program over from the RESET entry point (ie reboot)
ROM:F887                                         ; NOTE : RESET will clobber the log stored at 0x100 and 0x200
ROM:F88B                 orcc    #$10            ; disable IRQ
ROM:F88D                 lda     #$80 ; 'Ç'
ROM:F88F 
ROM:F88F LoopUntilVsyncIsInactive:               ; CODE XREF: SyncIrqCountToVsyncEnd+12 j
ROM:F88F                 bita    PIA_B_DATA      ; read bit 7 from PR-8210A (VSYNC)
ROM:F892                 bne     LoopUntilVsyncIsInactive ; if bit 7 is set, loop until it's clear (ie until vsync signal is inactive)
ROM:F894                 lda     PIA_B_DATA
ROM:F897                 bita    #$40 ; '@'      ; test bit 6 (FIELD) from PIA B port
ROM:F897                                         ; NOTE : field is expected to be up to date when vsync goes inactive
ROM:F899                 bne     TopField        ; if bit 6 is set, it means field is ODD, and to branch
ROM:F899                                         ; NOTE : I think this branch will always be taken because this subroutine only gets called when the bottom field is active.
ROM:F89B                 lda     #4              ; even field
ROM:F89D                 bra     loc_F8A1

This routine will wait 20ms to see a vsync, which seems reasonable since vsync occurs every 16 ms.  However, it also enables IRQs at $F882!!  This means that it is not uncommon for the IRQ handler to be active when a vsync comes in.

Now on a real PR-8210A, the vsync pulse is quite long, so usually, the IRQ handler exits while vsync is still active.  But on Dexter, this vsync pulse is much shorter which means by the time the IRQ handler exits, vsync is no longer active, and the PIF ROM will reboot, starting at $F887.

The 20 ms timeout is really quite aggressive and I can't see any reason for it (I also can't see any reason to enable IRQs right before waiting for vsync).  I suspect that except on a brand new laserdisc player, this aggressive timeout could cause the game to crash needlessly.

My solution?  Increase the 20 ms timeout to something much larger.

ROM:F95E ; returns carry clear if vsync can be detected within ~20ms or carry set if timeout happens first
ROM:F95E 
ROM:F95E Wait20msForVsync:                       ; CODE XREF: RebootIfVsyncIsNotPresent P
ROM:F95E                                         ; ROM:F659 P ...
ROM:F95E                 pshs    a,b,x
ROM:F960                 ldx     #0
ROM:F963 
ROM:F963 loc_F963:                               ; CODE XREF: Wait20msForVsync+14 j
ROM:F963                 jsr     Wait_122uS      ; Delays about .122 milliseconds (confirmed on real hardware)
ROM:F966                 lda     PIA_B_DATA
ROM:F969                 bita    #$80 ; 'Ç'      ; test VSYNC bit
ROM:F96B                 bne     VsyncDetected   ; branch if vsync is active
ROM:F96D                 leax    1,x
ROM:F96F                 cmpx    #$AA ; '¬'      ; wait 20 ms to see vsync
ROM:F972                 bcs     loc_F963
ROM:F974                 lda     #$94 ; 'ö'
ROM:F976                 jsr     AddToLog        ; This writes a code (stored in A) to a buffer at 0x100, then writes the IRQCounter to a buffer at 0x200.
ROM:F976                                         ; It then appends "END" to the end of the buffer.
ROM:F976                                         ; Subsequent calls will append (ie write to 0x101 instead of 0x100, etc) so that the buffer is actually a log.
ROM:F976                                         ; 
ROM:F976                                         ; I believe the PRE_RESET function checks for a flag that this code writes to RAM and will output it
ROM:F976                                         ; (perhaps via the PR-8210A remote control port?) to an engineer who knew what they were doing could
ROM:F976                                         ; read the log.
ROM:F976                                         ; 0x84: have not received FIRQ command 2 (hello) see f63d
ROM:F976                                         ; 0x89: received FIRQ command 2 (hello) see fca4
ROM:F976                                         ; 0x8B: watchdog counter exceeded limit (IRQ starvation)
ROM:F976                                         ; 0x8D: NMI called (video squelch)
ROM:F976                                         ; 0x8E: REJECT command has succeeded, disc is stopped (not an error) 0xf634
ROM:F976                                         ; 0x90: sanity check failed
ROM:F976                                         ; 0x91: sanity check failed inside IRQ
ROM:F976                                         ; 0x93: $F823 (sync irq counter to field) started
ROM:F976                                         ; 0x94: vsync never detected (f974)
ROM:F976                                         ; 0x95: stand by was raised when it shouldn't have been (f993)
ROM:F976                                         ; 0x96: SWI2 occurred
ROM:F976                                         ; 0x97: SWI3 occurred
ROM:F979                 orcc    #1              ; set carry
ROM:F97B                 bra     IsVsyncPresentReturn
ROM:F97D ; ---------------------------------------------------------------------------
ROM:F97D 
ROM:F97D VsyncDetected:                          ; CODE XREF: Wait20msForVsync+D j
ROM:F97D                 andcc   #$FE ; '¦'      ; clear carry
ROM:F97F 
ROM:F97F IsVsyncPresentReturn:                   ; CODE XREF: Wait20msForVsync+1D j
ROM:F97F                 puls    x,b,a
ROM:F981                 rts
ROM:F981 ; End of function Wait20msForVsync

I changed $F96F so instead of waiting until the counter (stored in X) reaches #$AA, I wait until it reaches #$3FF.  Why #$3FF?  This choice is somewhat arbitrary.  I just picked a value that covers many fields instead of just 1 field.

This change did the trick and now Dexter seems to be working perfectly with Star Rider!  Woohoo!

For those curious, there is a function called at $F963 which sleeps for about .122ms and #$AA is 170 in decimal, and 170 * 0.122 is 20.74 which is where I calculated that this method waits about 20 ms for vsync to appear.

The change to make to the PIF ROM to increase this timeout value:
  • Change address 0x005 from #$F2 to #$4A .  This makes it so the internal checksum test still passes.
  • Change address 0x970 and 0x971 from 0x00 0xAA to 0x3 0xFF

Friday, August 11, 2017

Dexter image quality bug discovered, fix is available now!

I've suspected for a while that something was a little off with Dexter's image quality.  However, this is almost unnoticeable on a CRT, so I haven't prioritized investigating it.  Now that I am trying to finish Star Rider support, which relies on data being decoded from the video signal, I looked into video quality issues further and found a couple of minor problems.

After some troubleshooting, I found that there are two issues with Dexter's image quality:
1) the video is shifted one line from what it should be,
2) the video has an unfocused look

Issue #1 is caused by the raspberry pi itself.  I don't have a "proper" fix, but I can compensate by shifting the video 1 line within the Dexter software which seems to do the trick.

Issue #2 is caused because I am using bilinear filtering inside of the OpenGLES2 code that Dexter is using.  Combined with my shader to implement interlaced video, this was creating a bunch of subtle artifacts that give the unfocused look.  I turned off the bilinear filtering and that did the trick!

I've fixed both of these issues internally and am working on a public fix for everyone.  It will be free for all Dexter customers, new and old.

UPDATE: the fix is live now! :)

Here are two images showing before and after.

Before

After (ignore the extra text)

Please ignore the "Current video line shift" and "Scale factor" text in the second screenshot.  Those are debug messages that I added while troubleshooting this problem.

Monday, July 17, 2017

Esh bug explained

In my previous blog post, I mentioned that there is a bug on Esh's Aurunmilla stage 7.  The bug is that if you lose all of your lives, the game over screen does not play properly.

The reason for this is that the game over screen starts at picture number 23253, and Esh's mistakenly tells the laserdisc player to autostop once it reaches picture number 23247 which is before the game over section starts.

The incorrect autostop picture number, 23247, is stored as hex 0x5ACF at memory location $215E in the Esh ROM.

Friday, July 14, 2017

Where Esh's Aurunmilla stores its 'current stage'

There's been some discussion about an Esh's Aurunmilla bug that shows up on stage 7.

Out of curiosity, I dug into the ROM program a little bit to see how hard it would be to modify the program to start on stage 7 to make testing easier.  After all, no one wants to play all the way to stage 7 just to test a fix!

I found that the stage value is stored at 0xE424 + whatever value is at 0xE3C1.  Here is code at 0x3d06 that prints out the current stage (before each stage):

seg000:3D06 print_current_stage:                    ; CODE XREF: seg000:3B4C p
seg000:3D06                 push    af
seg000:3D07                 push    bc
seg000:3D08                 push    de
seg000:3D09                 push    hl
seg000:3D0A                 call    sub_0_BC7
seg000:3D0D                 ld      a, 1
seg000:3D0F                 call    sub_0_3AAC
seg000:3D12                 ld      b, 1
seg000:3D14                 ld      hl, stage_number_txt
seg000:3D17                 call    pre_print_text
seg000:3D1A                 ld      hl, 0E424h      ; prepare to load current stage data
seg000:3D1D                 ld      a, (0E3C1h)     ; load optional offset
seg000:3D20                 ld      c, a
seg000:3D21                 ld      b, 0
seg000:3D23                 add     hl, bc          ; HL now contains address that contains stage value
seg000:3D24                 ld      l, (hl)         ; load stage value into L
seg000:3D25                 ld      h, 0
seg000:3D27                 ld      b, 14h
seg000:3D29                 ld      c, 0Eh
seg000:3D2B                 ld      e, 3            ; 3 digits for the stage number
seg000:3D2D
seg000:3D2D while_stage_not_printed:                ; CODE XREF: print_current_stage+3D j
seg000:3D2D                 push    bc
seg000:3D2E                 ld      b, 10           ; base 10 conversion
seg000:3D30                 call    hex2dec         ; Hex conversion
seg000:3D30                                         ; Src number is in HL.
seg000:3D30                                         ; Base will in B (ie 10 for base 10 conversion).
seg000:3D30                                         ; Returns the lowermost digit in A and HL will be have been divided by the base (ie if HL originally contained 3387 decimal, it will return as 338 decimal with 7 in A).
seg000:3D30                                         ;
seg000:3D33                 pop     bc
seg000:3D34                 push    de
seg000:3D35                 add     a, 30h ; '0'    ; convert to ASCII?
seg000:3D37                 ld      d, a
seg000:3D38                 ld      e, 9
seg000:3D3A                 ld      a, 1
seg000:3D3C                 call    print_letter    ; prints one letter to the screen
seg000:3D3F                 pop     de
seg000:3D40                 dec     b
seg000:3D41                 dec     b
seg000:3D42                 dec     e               ; decrement digits left count
seg000:3D43                 jr      nz, while_stage_not_printed
seg000:3D45                 pop     hl
seg000:3D46                 pop     de
seg000:3D47                 pop     bc
seg000:3D48                 pop     af
seg000:3D49                 ret

The stage value is incremented around 0xD7D:

seg000:0D7D loc_0_D7D:                              ; CODE XREF: seg000:0D6A j
seg000:0D7D                                         ; seg000:0D79 j
seg000:0D7D                 ld      hl, 0E424h      ; base address where current stage lives
seg000:0D80                 ld      a, (0E3C1h)
seg000:0D83                 and     a
seg000:0D84                 jr      z, loc_0_D87    ; this can increment the current stage
seg000:0D86                 inc     hl
seg000:0D87
seg000:0D87 loc_0_D87:                              ; CODE XREF: seg000:0D84 j
seg000:0D87                 inc     (hl)            ; this can increment the current stage

The stage value is initially set at:

seg000:0E69                 ld      hl, 0
seg000:0E6C                 ld      (0E424h), hl    ; set current stage to be 0

So to force the game to start on stage 7 instead of 0, one must do this:

Change the "ld hl,0" at 0xE69 to "ld hl,7".  Or change 0x21 0x00 0x00 to 0x21 0x07 0x00.

Tuesday, April 4, 2017

Modifying Star Rider to send triggers to logic analyzer

Star Rider has a RAM that was never installed, U7.

If I write to this address, I can trigger my logic analyzer to start capturing exactly where I want it.

But I need to modify the ROM to make this happen.

Some ideas ...

ROM1:1B68 ThinkActualFrameNumIsExpected:          ; CODE XREF: SendPifCmdsAndMore+5E P
ROM1:1B68                                         ; SendPifCmdsAndMore+98 P
ROM1:1B68                 lda     SlowPifCounter  ; may throttle how often we check for seek complete among other things
ROM1:1B6B                 beq     CompareActualPicNumWithExpected
ROM1:1B6D                 dec     SlowPifCounter  ; may throttle how often we check for seek complete among other things
ROM1:1B70                 beq     ReadActualPicNumIfAvailable
ROM1:1B72                 jsr     QueueNopCmdToPif ; make sure that this gets called again
ROM1:1B75                 clra                    ; sets Z flag to indicate success
ROM1:1B76                 rts
ROM1:1B77 ; ---------------------------------------------------------------------------
ROM1:1B77
ROM1:1B77 ReadActualPicNumIfAvailable:            ; CODE XREF: ThinkActualFrameNumIsExpected+8 j
ROM1:1B77                 lda     #4
ROM1:1B79                 sta     SlowPifCounter  ; may throttle how often we check for seek complete among other things
ROM1:1B7C                 dec     SlowerPifCounter
ROM1:1B7F                 beq     Error507        ; if this counter expires, it's an error
ROM1:1B81                 lda     FieldsLeftBeforeStability ; If this is 0, it means it's safe to read the VBI picture number. (See 1B81)
ROM1:1B81                                         ; Gets decremented by IRQ if it is not 0.
ROM1:1B81                                         ; It indicates how many fields the IRQ needs to see before it considers the software/hardware field values to be stable. (see e488)
ROM1:1B81                                         ; It gets set to non-zero any time the hardware field value does not match the software field value.
ROM1:1B84                 bne     Skip4TracksForward ; skip 4 tracks forward
ROM1:1B84                                         ; (to recover from bad VBI read?)
ROM1:1B86                 jsr     ConvertBCDPicNumToHexPicNum ; Converts the last read BCD picture number (from laserdisc VBI) to a hex picture number.
ROM1:1B86                                         ; Carry will be clear on success or set on failure.
ROM1:1B86                                         ; 'D' and $A133 will contain the converted picture number, or 50000 (decimal) on failure.
ROM1:1B89                 bcc     CompareActualPicNumWithExpected
ROM1:1B8B
ROM1:1B8B Skip4TracksForward:                     ; CODE XREF: ThinkActualFrameNumIsExpected+1C j
ROM1:1B8B                 lda     #$10            ; skip 4 tracks forward
ROM1:1B8B                                         ; (to recover from bad VBI read?)
ROM1:1B8D                 sta     PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1B8D                                         ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1B90                 clra
ROM1:1B91                 rts

I am going to try writing to D800 at 1B7C and 1B8B to see if it reveals anything.

Monday, April 3, 2017

Major Star Rider / PR-8210A / Dexter break through!

I'm pleased to announce that I've finally made a break through with regards to understanding in a very detailed way how Star Rider and the PR-8210A interact with each other, and thus how to properly support Star Rider with Dexter.

In order to explain why the knowledge I've gained is significant, I need to step back and give some context.

Star Rider is the only laserdisc game that I know of that uses the PR-8210A.  How is the PR-8210A different from a regular PR-8210?  The PR-8210 (and also PR-8210A) uses commands which are relatively slow to be transmitted.  A command must be sent 2-3 times (all of the games send it 3 times) in a row in order for it to be accepted by the player and this can take a complete frame or two if the disc is playing.  The PR-8210A exposes some internal signals on a centronics-24 connector (the PR-7820 and LD-V1000 also use this connector) that allow a connecting device (such as an arcade PCB) to control the forward/backward motion of the disc both quickly and accurately.  This allows one to develop the kind of game that Star Rider is: a racing game where the disc speeds up and slows down.  The way that this is done is via a signal called the Jump Trigger.

What is the jump trigger?  It's normally an internal signal that tells the disc whether to jump ahead a track or backward a track.  For example, if the disc is paused, at the beginning of every frame, the player will internally generate a jump trigger to tell the disc to jump back 1 frame.  This creates the illusion to the viewer that the disc isn't moving at all but it actually is moving, it is just skipping backward continuously to achieve this illusion.  The jump trigger is also used for stuff like 2X and 3X forward and reverse playback.

Since the PR-8210 commands are so slow, as I mentioned before, they are not suitable for a racing type game.  However, if the game (Star Rider) can instead control the jump trigger, then a racing game suddenly becomes possible.  This is what the PR-8210A provides that a normal PR-8210 does not.

So why have I been having so much trouble with Dexter and Star Rider?  It's because the exact behavior of the jump trigger is not documented (at least not where I can find) so I basically have to take some guesses and/or observe how a real player works.

For the past N years, I've been doing the former, taking educated guesses, and getting close.  But I was running into things like the jump trigger getting sent at odd times from the game and not understanding how Dexter should respond in this situation.  So I finally broke down and got a real PR-8210A setup going (as shown in a previous blog post).  Coupled with a logic analyzer, I was able to finally see what I've been missing for years!

First, how does a search look?


Star Rider will send each command three times in a row.  It looks something like this if it wanted to search to frame 68:
seek seek seek
disp disp disp null
0 0 0 null
0 0 0 null
0 0 0 null
6 6 6 null
8 8 8 null
seek seek seek

Notice "Z2-5 jtrg in" from the above screenshot.  This is the internally generated jump trigger from the player that comes out to the external centronics-24 connector on the back of the player.  It is maintaining the disc in a paused state during the incoming search command.  Notice "Z2-6 jtrg out."  It is the 'final' jump trigger signal that the player actually acts upon.  The Star Rider PCB has the opportunity to override this signal and provide its own.  In this case, Star Rider is not overriding the signal (jtrig int/ext', line #1, is raised) and so what comes into the centronics-24 connector is the same thing that goes back out.

What happens during a search?



A few things happen here:

  • "Stand by" starts toggling at approximately 2 Hz.
  • Video sq' (video squelch) goes low.
  • Z2-5 and Z2-6 start becoming very active.  The player actually uses jump triggers in order to perform a search.  Neat, eh?

What happens when a search completes?



  • Video squelch goes high.
  • The player starts internally generating jump triggers via Z2-5 again (ie the disc is in a paused state)


Notice toward the end of the above screenshot, Star Rider sends three play commands.  This is where I was getting hung up because it was very unclear to me what would happen here.  I could tell that Star Rider wanted take over jump trigger duties and that by putting the player in the 'playing' state that the player would stop generating internal jump triggers.  But I couldn't tell whether there was a bug in Star Rider that caused the player to jump an extra track forward or backward.

Examining these three play commands more closely is very interesting.


In the high-lighted area, the player is generating internal jump triggers and Star Rider is also generating them AND trying to override the player's internal jump triggers.  This appears to be a defect in Star Rider's program.  It should not be trying to do this.  But it is what it is.  There seems to be a contention here.  So what actually happens?  Does the player process two jump triggers?  Does Star Rider manage to override the internal jump trigger before it kicks off?  Is a second jump trigger ignored?  It's really hard to say... unless we zoom in :)


This may seem to be a straight forward screenshot of straight forward information, but let me assure you, it took me YEARS to finally figure this out!  I can only guess, but my speculation is that the Star Rider developers were not aware of this bug in their code because, conveniently, the PR-8210A apparently ignores the second jump trigger.    The reason that this is a bug is because Star Rider overrides the PR-8210A's jump trigger after the PR-8210A has already generated it.  Why does the PR-8210A ignore the second jump trigger?  Perhaps because it occurs to close to the first one?  I really have no way of knowing without further study.

Conclusion: I should be able to make Dexter ignore an external jump trigger from Star Rider the same way that the PR-8210A ignores it.  It will take a bit of trial and error, but I am confident that I can make it happen.  Having the original player available to study was extremely helpful!