Sunday, November 29, 2015

Dexter pre-order poll

When should I take pre-orders for the final version of Dexter?  December is a busy time of year, so I am leaning toward January, but maybe December would be better.  What do you think?

Vote here .

Results here .

Tuesday, November 24, 2015

I've been having email problems.

I've had email problems for at least a month with the email account linked to my Dexter "let me know when it's ready" link on the Dexter web page.  So if you've tried to send me an email that way, I probably did not receive it.  I'm very sorry for the inconvenience.  It should be fixed now!

Thursday, November 19, 2015

Star Rider clever FIRQ handler revealed

In furtherance of my goal to get Star Rider working with Dexter, I decoded the FIRQ handler in the Star Rider main CPU's ROM.  FIRQ stands for Fast IRQ and is an alternate interrupt available on the 6809E CPU which is "fast" because it does not push all of the registers on the stack before executing (meaning the programmer will have to assume this responsibility instead but has the option to use the registers cleverly to squeeze out some extra performance).

The Star Rider FIRQ handler is pretty nifty because it is designed to be called when the hardware blitter "special" chips have finished an operation and immediately give the blitter "special" chips more work to do.  The neat thing about this design is that the main CPU can still be doing other work while the blitter chips are running.  In previous Williams' games, such as Joust, the blitter chip halts the main CPU and no work can be done while the blitter chip is doing its thing.  With Star Rider, the hardware guys (apparently) realized that halting the main CPU was a potential waste of time and instead let the main CPU keep running while simultaneously disabling the main CPU's access to the video/DMA bus.  It's a really clever design for 1983/1984 or whenever.

Also cool is that the FIRQ handler can be called without an interrupt occurring (see $EAD9).  If called in this way, the program will mimic what the CPU does for a real FIRQ so that the RTI (return from interrupt) instruction works correctly regardless of whether there was a real FIRQ or if the program faked it.

Finally, the FIRQ handler is even more nifty because it has an optional "wait for vsync" type behavior built-in.  It can optionally check to see whether the next blit will cause visible vertical tearing and if so delay the blit until this is no longer a concern.  I am not sure if this code actually ends up getting used in production because, just from eyeballing it, I am not 100% convinced that the vertical counter hardware value matches up with the actual vertical line value (maybe it does!) and the ROM program definitely seems to be making this assumption.  Decide for yourself :)

ROM:EAD9 ; =============== S U B R O U T I N E =======================================
ROM:EAD9
ROM:EAD9 ; Execute the FIRQ interrupt handler without the FIRQ interrupt firing.
ROM:EAD9 ; This appears to create an environment where RTI will act like RTS.
ROM:EAD9 ; NOTE: it appears that IRQ's are not disabled
ROM:EAD9
ROM:EAD9 FakeFIRQ:                               ; CODE XREF: DoBlit:loc_0_EA90 P
ROM:EAD9                                         ; sub_0_EAB4+18 P
ROM:EAD9                 tfr     cc, a
ROM:EADB                 anda    #$7F ; ''      ; disable E flag (for subsequent RTI call)
ROM:EADD                 pshs    a               ; save CC flags (For subsequent RTI call)
ROM:EADF                 orcc    #%1000000       ; disable F flag (for subsequent RTI call)
ROM:EAE1
ROM:EAE1 FIRQ:                                   ; DATA XREF: ROM:FFF6 o
ROM:EAE1                 tst     PIA_VGG_U7_PERFDIR_A ; clear PIA's IRQA'
ROM:EAE4                 dec     BlitterPendingOperationsCount ; When FIRQ gets called, it means a blitter operation just finished.
ROM:EAE4                                         ; So decrement the pending count.
ROM:EAE7                 beq     FIRQ_End        ; branch if we have no more blitter operations left
ROM:EAE9                 pshs    a,b,x           ; save regs (since this is FIRQ, they won't be automatically saved)
ROM:EAEB                 ldx     BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EAEE                 lda     BlitDontAvoidTearing ; 0: try to avoid vsync tearing artifacts
ROM:EAEE                                         ; 1: blit as fast as possible ignoring vertical tearing
ROM:EAEE                                         ; (see EAEE)
ROM:EAF1                 bne     DoBlitAndAdvanceBlitterContextPtr ; X points to the blitter context
ROM:EAF3                 lda     8,x
ROM:EAF5                 bmi     DoBlitAndAdvanceBlitterContextPtr ; branch if IMPG value has high-bit set
ROM:EAF7                 lda     5,x             ; A = blitter destination Y value
ROM:EAF9                 adda    7,x             ; add blit size height to A
ROM:EAF9                                         ; This makes A now the bottom coordinate of the blit rectangle
ROM:EAFB                 suba    HW_VerticalCounter ; Subtract current vertical counter value from blit bottom Y value.
ROM:EAFB                                         ; If positive, beam is above bottom of blit can tearing can occur.
ROM:EAFB                                         ; If negative, beam is below bottom of blit and tearing is not a risk.
ROM:EAFB                                         ; AT LEAST that's what it is looking like to me! :)
ROM:EAFE                 bls     DoBlitAndAdvanceBlitterContextPtr ; branch if tearing is not a concern
ROM:EB00                 ldb     #65             ; this appears to create a new blit size
ROM:EB00                                         ; of up to 64x65 pixels, but can be less.
ROM:EB00                                         ; It appears to be a way to stall to avoid tearing artifacts on the screen.
ROM:EB02                 cmpa    #64
ROM:EB04                 bcs     DoNullBlit      ; if width is already less than 64 pixels, branch
ROM:EB06                 lda     #64             ; force width down to 64 pixels
ROM:EB08
ROM:EB08 DoNullBlit:                             ; CODE XREF: FakeFIRQ+2B j
ROM:EB08                 std     HW_BlitterSize
ROM:EB0B                 inc     BlitterPendingOperationsCount ; make sure next time FIRQ fires, the blit we didn't perform gets redone
ROM:EB0E                 lda     #%11000000      ; this appears to execute a NOP to the blitter chips by suppressing all writes.  It may be to queue up a future FIRQ.
ROM:EB10                 sta     HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB10                                         ; Control Bits
ROM:EB10                                         ;  7: 1=write suppress odd pixels
ROM:EB10                                         ;  6: 1=write suppress even pixels
ROM:EB10                                         ;  5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB10                                         ;  4: 1=solid color mode (constant substitution)
ROM:EB10                                         ;  3: 1=zero write suppress (only blit non-zero color values)
ROM:EB10                                         ;  2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB10                                         ;  1: write format (0=serial, 1=block)
ROM:EB10                                         ;  0: read format (0=serial, 1=block)
ROM:EB13                 bra     FIRQ_RestoreRegsThenEnd
ROM:EB15 ; ---------------------------------------------------------------------------
ROM:EB15
ROM:EB15 DoBlitAndAdvanceBlitterContextPtr:      ; CODE XREF: FakeFIRQ+18 j
ROM:EB15                                         ; FakeFIRQ+1C j ...
ROM:EB15                 ldd     8,x             ; X points to the blitter context
ROM:EB17                 std     HW_IMPG_PRIME
ROM:EB1A                 ldd     6,x
ROM:EB1C                 std     HW_BlitterSize
ROM:EB1F                 ldd     4,x
ROM:EB21                 std     HW_BlitterDest
ROM:EB24                 ldd     2,x
ROM:EB26                 std     HW_BlitterSource
ROM:EB29                 ldd     ,x
ROM:EB2B                 stb     HW_BlitterSolidClr
ROM:EB2E                 sta     HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB2E                                         ; Control Bits
ROM:EB2E                                         ;  7: 1=write suppress odd pixels
ROM:EB2E                                         ;  6: 1=write suppress even pixels
ROM:EB2E                                         ;  5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB2E                                         ;  4: 1=solid color mode (constant substitution)
ROM:EB2E                                         ;  3: 1=zero write suppress (only blit non-zero color values)
ROM:EB2E                                         ;  2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB2E                                         ;  1: write format (0=serial, 1=block)
ROM:EB2E                                         ;  0: read format (0=serial, 1=block)
ROM:EB31                 leax    10,x            ; advance to the next blitter context in the array
ROM:EB31                                         ; (each context is 10 bytes long)
ROM:EB33                 cmpx    #word_0_AC1C    ; have we got to the end of the blitter contexts?
ROM:EB36                 bcs     StoreNewBlitterContextPointer
ROM:EB38                 ldx     #BlitterContextArray ; a 1000 byte array which has room for 100 10-byte blitter context entries.
ROM:EB3B
ROM:EB3B StoreNewBlitterContextPointer:          ; CODE XREF: FakeFIRQ+5D j
ROM:EB3B                 stx     BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EB3E
ROM:EB3E FIRQ_RestoreRegsThenEnd:                ; CODE XREF: FakeFIRQ+3A j
ROM:EB3E                 puls    x,b,a
ROM:EB40
ROM:EB40 FIRQ_End:                               ; CODE XREF: FakeFIRQ+E j
ROM:EB40                 rti
ROM:EB40 ; End of function FakeFIRQ

Monday, November 16, 2015

Dexter Star Rider search bug solution

My birthday party is over (don't think that I mentioned that on here) so I now am turning my focus back to shipping Dexter.

Star Rider has almost worked perfectly with Dexter for quite some time and I had the opportunity to do more Star Rider research recently and here's some of my interesting findings.

First of all, the reason that Dexter wasn't working was because during the self-test, it would routinely fail with a search to frame 6 (as I recall) but would usually be able to successfully seek to other test frames on the disc.  Why was it always failing to seek to frame 6?

Well, after much studying of the ROM disassembly, I think I have a pretty good grasp of how it works.

First of all, to initiate a seek, the Star Rider program calls a function at $7E75:

ROM3:7E75 ; =============== S U B R O U T I N E =======================================
ROM3:7E75
ROM3:7E75 ; frame number stored in D
ROM3:7E75 ; on return carry may be set if advance button was pressed
ROM3:7E75
ROM3:7E75 InitiateDiscSearch:                     ; CODE XREF: DiscTestSearchTo+2F P
ROM3:7E75                                         ; ROM3:7DE7 P ...
ROM3:7E75                 pshs    x,y,u
ROM3:7E77                 pshs    a,b
ROM3:7E79                 clra
ROM3:7E7A                 clrb
ROM3:7E7B                 std     PifSendError    ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E7B                                         ; (see 19e2).  Otherwise it will be 0.
ROM3:7E7E                 lda     #$17
ROM3:7E80                 sta     PifResponseRetryCount
ROM3:7E83                 puls    b,a
ROM3:7E85                 jsr     SetupPifBuffersForSearching ; It appears that this is a "search to frame" function, where the frame is stored in D
ROM3:7E85                                         ; (A1BC needs to get a non-zero value stored to it or else it's an error)
ROM3:7E85                                         ; A1A4: 0x5 0x5 0x13 FrameHi FrameLo 0x5
ROM3:7E85                                         ; A1B4: FrameHi FrameLo
ROM3:7E85                                         ; A1BC: 0x13 0x0A 0x64
ROM3:7E85                                         ;
ROM3:7E85                                         ; A1A7 and A1B4 = D
ROM3:7E85                                         ; A1A4 = 0x505
ROM3:7E85                                         ; A1A6,A1BC = 0x13
ROM3:7E85                                         ; A1BD = 0xA64
ROM3:7E85                                         ; A1A9 = 5
ROM3:7E88
ROM3:7E88 WaitSearchFinish:                       ; CODE XREF: InitiateDiscSearch+21 j
ROM3:7E88                 clra
ROM3:7E89                 ldd     PifSendError    ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E89                                         ; (see 19e2).  Otherwise it will be 0.
ROM3:7E8C                 bne     Done
ROM3:7E8E                 jsr     IfAdvancePressedSetCarry ; set carry if advance is pressed
ROM3:7E8E                                         ; clear carry if advance not pressed
ROM3:7E91                 bcs     Done
ROM3:7E93                 jsr     TestExpectedFrameNumStatus ; Does a TST on memory location A1BC
ROM3:7E93                                         ; Z will be set if expected frame number matches actual frame number.
ROM3:7E96                 bne     WaitSearchFinish
ROM3:7E98                 ldd     PifSendError    ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E98                                         ; (see 19e2).  Otherwise it will be 0.
ROM3:7E9B
ROM3:7E9B Done:                                   ; CODE XREF: InitiateDiscSearch+17 j
ROM3:7E9B                                         ; InitiateDiscSearch+1C j
ROM3:7E9B                 puls    pc,u,y,x
ROM3:7E9B ; End of function InitiateDiscSearch

The summary is that this method calls another function at $7E85 to store data in some buffers that will be later sent to the PIF board and then it waits for either the search to succeed ($7E98) or for the search to fail with an error ($7E8C).

Here is the function at $7E85:

ROM1:1C5B ; =============== S U B R O U T I N E =======================================
ROM1:1C5B
ROM1:1C5B ; It appears that this is a "search to frame" function, where the frame is stored in D
ROM1:1C5B ; (A1BC needs to get a non-zero value stored to it or else it's an error)
ROM1:1C5B ; A1A4: 0x5 0x5 0x13 FrameHi FrameLo 0x5
ROM1:1C5B ; A1B4: FrameHi FrameLo
ROM1:1C5B ; A1BC: 0x13 0x0A 0x64
ROM1:1C5B ;
ROM1:1C5B ; A1A7 and A1B4 = D
ROM1:1C5B ; A1A4 = 0x505
ROM1:1C5B ; A1A6,A1BC = 0x13
ROM1:1C5B ; A1BD = 0xA64
ROM1:1C5B ; A1A9 = 5
ROM1:1C5B
ROM1:1C5B SetupPifBuffersForSearching:            ; CODE XREF: sub_0_35A0+C P
ROM1:1C5B                                         ; sub_0_4471+98 P ...
ROM1:1C5B                 pshs    cc
ROM1:1C5D                 orcc    #$F0 ; '='      ; suppress interrupts
ROM1:1C5F                 std     PifArray5Bytes_3
ROM1:1C62                 std     ExpectedHexPicNum ; The picture number we expect to be on (used to detect successful searches, etc).
ROM1:1C62                                         ; (see 1b9c)
ROM1:1C65                 ldd     #$505           ; 5 is a NOP command, so this starts with two NOP commands
ROM1:1C68                 std     PifArray5Bytes  ; this array will be sent to pif board if the first byt is not zero
ROM1:1C68                                         ; it is 5 bytes in size
ROM1:1C6B                 lda     #$13            ; 13 is the FIRQ cmd to initiate disc search
ROM1:1C6D                 sta     PifArray5Bytes_2
ROM1:1C70                 sta     ExpectedFrameNumStatus ; May indicate current/pending pif command. (see 1a5f)
ROM1:1C70                                         ; gets 0x13 stored when a new search is initiated (FIRQ search command)
ROM1:1C73                 ldd     #$A64           ; 0xA is a slow decrementing counter that determines when we start calling the subroutine at 1b68,
ROM1:1C73                                         ; while 0x64 is an even slower decrementing counter that, if it reaches 0, triggers a 507 error (see 1b7f)
ROM1:1C76                 stb     SlowerPifCounter
ROM1:1C79                 sta     SlowPifCounter  ; may throttle how often we check for seek complete among other things
ROM1:1C7C
ROM1:1C7C loc_0_1C7C:                             ; DATA XREF: sub_0_2DDA+19A r
ROM1:1C7C                 jsr     Store5ToA1A9    ; A1A9 = 5
ROM1:1C7F                 puls    pc,cc
ROM1:1C7F ; End of function SetupPifBuffersForSearching

This function was quite difficult to decode.  It basically stores some commands to be sent to the PIF board at a later time in some buffers.  Of particular note is a 5-byte array which will look like this: 0x05 0x05 0x13 FrameHi FrameLo.  By disassembling the PIF board ROM, I was able to discover that command 0x5 is a single byte NO-OP while command 0x13 is a 'search to frame' command which is followed by the desired frame number in binary/hex format.

So when does this command actually get executed?

It happens during the IRQ service routine, which gets triggered on vblank.

We'll skip some details and start at $1A5F which gets called every top field and checks to see if there's anything to send to the PIF board.  In our case, after the search command has been received, we will end up here:

ROM1:1A97 A1BCNotZeroAmongOtherTHings:            ; CODE XREF: SendPifCmdsAndMore+5 j
ROM1:1A97                                         ; SendPifCmdsAndMore+A j ...
ROM1:1A97                 orcc    #$F0 ; '='      ; disable interrupts
ROM1:1A99                 lda     PIA_VGG_U7_CTRL_B ; PIF PIA
ROM1:1A9C                 lbpl    DecreaseCounterAndCheck ; branch if pif board is not ready / waiting for seek to finish (?)
ROM1:1AA0                 ldx     NextPifBufStruct ; seems to contain a pointer to the next pif buffer structure after pif board becomes ready again (see 1AA0)
ROM1:1AA3                 beq     DecrementPriorityCounter ; branch if there is no active policy
ROM1:1AA5                 ldb     PifTimeoutBytesLeftToSend ; do we need to finish sending pif bytes we tried to send previously?
ROM1:1AA8                 beq     NoPifBytesLeftToSend ; if we have no bytes left to send from a previous attempt, branch
ROM1:1AAA                 ldu     #PifTimeoutSendBuf ; If sending to pif timed out, this buffer
ROM1:1AAA                                         ; will hold what we still need to send after
ROM1:1AAA                                         ; the pif board becomes ready again.
ROM1:1AAA                                         ; The array size is 4 bytes because the max
ROM1:1AAA                                         ; size of a pif send buffer is 5, so at
ROM1:1AAA                                         ; most 4 bytes will not have been sent if
ROM1:1AAA                                         ; the pif board becomes busy.
ROM1:1AAD                 jsr     SendCommandsToPif ; B holds how many bytes to send and will be modified
ROM1:1AAD                                         ; U points to array of bytes to send and will be modified
ROM1:1AAD                                         ; carry is set if we don't get the expected response
ROM1:1AB0                 bcs     OnPifTimeout    ; if we timed out, it can mean that a seek has initiated (maybe can mean other things)
ROM1:1AB0                                         ; and so we should wait for the pif board to become ready
ROM1:1AB0                                         ; before sending anything else (within reason timeout period).

$1A99 checks to see if the PIF board is 'ready' or not.  If it returns a result with the high-bit clear, it means the PIF board is not ready, so the program branches to the end of this function and just decrements a timer.  If this timer expires, the PIF board is assumed to have become unresponsive and a fatal error is thrown.  It is pretty important to understand that as long as the PIF board is 'busy', this method won't try to check to see whether the laserdisc is on the proper frame.

So once the PIF board has become non-busy, the program will then try to match up the frame number.

ROM1:1B68 ; =============== S U B R O U T I N E =======================================
ROM1:1B68
ROM1:1B68 ; If enough time has passed, check to see if the expected picture number equals the actual picture number.
ROM1:1B68 ; If there is a discrepancy, send PIF jump commands to compensate.
ROM1:1B68 ; If enough time hasn't passed, this will update the timer/counter.
ROM1:1B68 ;
ROM1:1B68 ; This sets Z flag on success (D = 0) and return an error code in D on error.
ROM1:1B68
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     Store5ToA1A9    ; 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
ROM1:1B92 ; ---------------------------------------------------------------------------
ROM1:1B92
ROM1:1B92 Error507:                               ; CODE XREF: ThinkActualFrameNumIsExpected+17 j
ROM1:1B92                 ldd     #$507           ; this may be an error code
ROM1:1B95                 rts
ROM1:1B96 ; ---------------------------------------------------------------------------
ROM1:1B96
ROM1:1B96 CompareActualPicNumWithExpected:        ; CODE XREF: ThinkActualFrameNumIsExpected+3 j
ROM1:1B96                                         ; ThinkActualFrameNumIsExpected+21 j
ROM1:1B96                 clr     SlowPifCounter  ; may throttle how often we check for seek complete among other things
ROM1:1B99                 ldd     CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1B99                                         ; (see 6f3)
ROM1:1B99                                         ; Gets 50000 (decimal) stored if VBI frame number appears to be  invalid.
ROM1:1B99                                         ; (see 6E4)
ROM1:1B9C                 subd    ExpectedHexPicNum ; The picture number we expect to be on (used to detect successful searches, etc).
ROM1:1B9C                                         ; (see 1b9c)
ROM1:1B9F                 beq     ActualFrameNumMatchesExpectedFrameNum ; This gets cleared when the expected frame number matches the actual frame number. (see $1BDD)
ROM1:1B9F                                         ; This is used by the disc "search to" test to indicate that an expected frame number was detected by the VBI reader.
ROM1:1BA1                 bhi     ActualPicNumGreaterThanExpected ; are we off by more than 2 tracks?
ROM1:1BA3                 coma                    ; negate the delta (I am pretty sure, but I haven't tested it)
ROM1:1BA3                                         ; so it is positive instead of negative
ROM1:1BA4                 negb
ROM1:1BA5                 sbca    #$FF
ROM1:1BA7                 cmpd    #4              ; is delta greater than 4?
ROM1:1BAB                 bcs     SendSkipForwardCommand
ROM1:1BAD                 ldd     #4              ; assume delta is 4 if it is larger than 4 because we only have 4 skip forward commands available to us.
ROM1:1BB0
ROM1:1BB0 SendSkipForwardCommand:                 ; CODE XREF: ThinkActualFrameNumIsExpected+43 j
ROM1:1BB0                 pshs    b
ROM1:1BB2                 addd    CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BB2                                         ; (see 6f3)
ROM1:1BB2                                         ; Gets 50000 (decimal) stored if VBI frame number appears to be  invalid.
ROM1:1BB2                                         ; (see 6E4)
ROM1:1BB5                 std     CurrentHexPicNumber ; Adjust current picture number under the assumption that the
ROM1:1BB5                                         ; track jump will be successful.
ROM1:1BB5                                         ; We need to track it in case we have to perform multiple track jumps in a row.
ROM1:1BB8                 puls    b
ROM1:1BBA                 addb    #$C             ; send one of 4 PIF 'skip forward' commands,
ROM1:1BBA                                         ; either 0xD, 0xE, 0xF, or 0x10
ROM1:1BBC                 stb     PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1BBC                                         ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1BBF                 clra
ROM1:1BC0                 rts
ROM1:1BC1 ; ---------------------------------------------------------------------------
ROM1:1BC1
ROM1:1BC1 ActualPicNumGreaterThanExpected:        ; CODE XREF: ThinkActualFrameNumIsExpected+39 j
ROM1:1BC1                 cmpd    #2              ; are we off by more than 2 tracks?
ROM1:1BC5                 bcs     SendSkipBackwardCommand ; save difference
ROM1:1BC7                 ldd     #2              ; Assume we are off by 2 if it's more than that.
ROM1:1BC7                                         ; This is because there are only two reverse PIF commands,
ROM1:1BC7                                         ; 0x11 and 0x12 so our difference value must be
ROM1:1BC7                                         ; either 1 or 2 since we add to 0x10 below.
ROM1:1BCA
ROM1:1BCA SendSkipBackwardCommand:                ; CODE XREF: ThinkActualFrameNumIsExpected+5D j
ROM1:1BCA                 pshs    a,b             ; save difference
ROM1:1BCC                 ldd     CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BCC                                         ; (see 6f3)
ROM1:1BCC                                         ; Gets 50000 (decimal) stored if VBI frame number appears to be  invalid.
ROM1:1BCC                                         ; (see 6E4)
ROM1:1BCF                 subd    ,s
ROM1:1BD1                 std     CurrentHexPicNumber ; Adjust current frame number as if the track jump has already occurred and is successful.
ROM1:1BD1                                         ; We need to track this here in case we need to perform multiple
ROM1:1BD1                                         ; track  jumps in a row.
ROM1:1BD4                 puls    b,a             ; restore difference
ROM1:1BD6                 addb    #$10            ; this converts the difference value into one of the two PIF 'jump backward' commands (0x11 or 0x12).
ROM1:1BD8                 stb     PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1BD8                                         ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1BDB                 clra
ROM1:1BDC                 rts
ROM1:1BDD ; ---------------------------------------------------------------------------
ROM1:1BDD
ROM1:1BDD ActualFrameNumMatchesExpectedFrameNum:  ; CODE XREF: ThinkActualFrameNumIsExpected+37 j
ROM1:1BDD                 clr     ExpectedFrameNumStatus ; This gets cleared when the expected frame number matches the actual frame number. (see $1BDD)
ROM1:1BDD                                         ; This is used by the disc "search to" test to indicate that an expected frame number was detected by the VBI reader.
ROM1:1BE0                 lda     #$F
ROM1:1BE2                 sta     word_0_A1C0     ; Gets set to 0xF when expected pic number matches actual pic num.
ROM1:1BE2                                         ; (see 1be2)
ROM1:1BE5                 ldd     CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BE5                                         ; (see 6f3)
ROM1:1BE5                                         ; Gets 50000 (decimal) stored if VBI frame number appears to be  invalid.
ROM1:1BE5                                         ; (see 6E4)
ROM1:1BE8                 std     SearchedHexPicNumber ; Gets picture number after a successful search.
ROM1:1BE8                                         ; (see 1be8)
ROM1:1BEB                 clr     byte_0_A1B8     ; Gets cleared when expected picture number matches actual picture number
ROM1:1BEB                                         ; (see 1beb)
ROM1:1BEE                 clra
ROM1:1BEF                 rts
ROM1:1BEF ; End of function ThinkActualFrameNumIsExpected

Again, this function was quite difficult to understand and took me a long time.

$1B68 checks to see whether it's time to read the real frame number from the video signal.

If it's time, it reads the frame number at $1B86 and converts it from the BCD laserdisc format to regular binary/hex format.

If the frame number cannot be converted from BCD because it contains 'hex' digits, or if it is not a valid VBI picture number, or if it times out, it will try to skip 4 tracks forward and retry ($1B8B).

$1B96 is the routine that actually compares the two frame numbers (actual vs expected).  The expected picture number will be subtracted from the actual picture number.  If the difference is 0, the search is assumed to be successful ($1BDD).  Otherwise, the program will attempt to _skip_ to the right location (which is pretty nifty).  It will either skip forward ($1BB0) or backward ($1BCA).

CONCLUSION:
To fix Dexter, I probably need to make sure that I am sending correct picture numbers in the VBI signal a few fields/frames before I send the 'search has finished' signal.  This will allow the frame decoding hardware on the VGG board time to get a good read before the ROM program tries to validate whether it is correct.  Alternatively, I can make all seeks completely instantly for PR-8210A mode which is probably a lot safer but not as autentic.