Wednesday, August 24, 2016

All Dexters now shipped!

It took me from the end of April to the end of August, but I've now shipped every Dexter unit from the Jan-Feb 2016 pre-order.  Hopefully, you will agree that me that it was worth the wait. :)

I now need to focus on selling my house and moving.  Once I've done this, I should have some time to sell some more Dexter units and do some Dexter+Merlin updates.

Monday, July 25, 2016

Merlin + Dexter integration update

It sounds like after seeing Dexter and Merlin in action at California Extreme that people are really chomping at the bit to get this installed into their cabs.  While I can appreciate that people are excited, I also need to be realistic about timing and estimates.  Right now, I am still shipping out Dexter units (yes, still!) and then need to move to a new house (my wife has been patient with Dexter and now it's her turn to get what she wants).  So I am going to estimate that I will have my pat of Dexter + Merlin integration finished by the end of 2016.  Not what some people were hoping, but I prefer to give real estimates than to keep missing my dates.

Friday, June 17, 2016

Dexter orders won't be shipping next week

Hi guys,

I am taking a week off from shipping out Dexter orders.  The shipping will resume on 27 June.  Sorry for the delay and thanks for your patience.  Hopefully people who have received their orders already can agree that it is worth the wait. :)

Wednesday, June 8, 2016

How I fixed Dexter's Firefox problem

So Firefox was working fine with Dexter except for one thing: Firefox's built-in disc test.

I decided that I would need to study the disc test in detail to see what it was doing that Dexter was not supporting.  Here is the disassembled version of the disc test (warning, it's long!!)

ROM:F238 DISK1:                                  ; CODE XREF: ROM:EE68 P
ROM:F238                 ldb     #$63 ; 'c'
ROM:F23A                 lda     <DQ_WTO         ; write timeout counter
ROM:F23C                 bne     DispWriteError
ROM:F23E                 jsr     UNMESS
ROM:F241                 bra     PostWriteErrCheck ; display frame read
ROM:F243 ; ---------------------------------------------------------------------------
ROM:F243
ROM:F243 DispWriteError:                         ; CODE XREF: DISK1+4 j
ROM:F243                 jsr     NWMESS
ROM:F246
ROM:F246 PostWriteErrCheck:                      ; CODE XREF: DISK1+9 j
ROM:F246                 ldy     #PFRW20_30      ; display frame read
ROM:F24A                 ldu     #DQ_INB         ; input buffer
ROM:F24D                 jsr     VW6DIG          ; display 6 digits with zero suppression
ROM:F250                 ldy     #PFRW25_30      ; display frame desired
ROM:F254                 ldu     #DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F254                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F257                 jsr     VW6DIG          ; display 6 digits with zero suppression
ROM:F25A                 lda     NVRAMZ_R_BYT0   ; switches are active low
ROM:F25D                 anda    #IN0_RightThumb
ROM:F25F                 beq     EnableDiscReset ; turn disk reset on
ROM:F261                 lda     #DRSTOFF        ; leave disk alone
ROM:F263                 bra     PostRightThumbCheck
ROM:F265 ; ---------------------------------------------------------------------------
ROM:F265
ROM:F265 EnableDiscReset:                        ; CODE XREF: DISK1+27 j
ROM:F265                 lda     #DRSTON         ; turn disk reset on
ROM:F267
ROM:F267 PostRightThumbCheck:                    ; CODE XREF: DISK1+2B j
ROM:F267                 sta     DSKRST          ; aka W.RSTD
ROM:F267                                         ; disk reset (active low) (at least 2 ms)
ROM:F26A                 lda     <RL_DTC
ROM:F26C                 sta     W_AULD
ROM:F26F                 lda     <RHTCHN
ROM:F271                 sta     W_AURD
ROM:F274                 ldb     #$61 ; 'a'      ; "READ ERROR"
ROM:F276                 lda     <DQ_RTO         ; read timeout counter
ROM:F278                 beq     OnNoReadError   ; erase "READ ERROR"
ROM:F27A
ROM:F27A DisplayReadError:                       ; CODE XREF: DISK1+52 j
ROM:F27A                 jsr     NWMESS          ; display "READ ERROR"
ROM:F27D                 bra     OnError
ROM:F27F ; ---------------------------------------------------------------------------
ROM:F27F                 bra     Disc1Return
ROM:F281 ; ---------------------------------------------------------------------------
ROM:F281
ROM:F281 OnNoReadError:                          ; CODE XREF: DISK1+40 j
ROM:F281                 jsr     UNMESS          ; erase "READ ERROR"
ROM:F284                 ldb     #$61 ; 'a'
ROM:F286                 lda     <DQ_LED         ; lead in if >0, lead out if >=80
ROM:F288                 beq     NotLeadInOrLeadOut
ROM:F28A                 bpl     DisplayReadError ; cannot be LEAD-IN, display "READ ERROR"
ROM:F28C                 ldd     #2              ; if we get this far, it means we encountered the LEAD-OUT area
ROM:F28F                 std     JMPCT           ; back up 2 at a time
ROM:F292                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F292                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F294                 anda    #7              ; clear play forward bit
ROM:F296                 sta     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F296                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F298                 jmp     $32JumpBack     ; jump back
ROM:F29B ; ---------------------------------------------------------------------------
ROM:F29B
ROM:F29B Disc1Return:                            ; CODE XREF: DISK1+47 j
ROM:F29B                                         ; DISK1+7A j ...
ROM:F29B                 rts
ROM:F29C ; ---------------------------------------------------------------------------
ROM:F29C
ROM:F29C NotLeadInOrLeadOut:                     ; CODE XREF: DISK1+50 j
ROM:F29C                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F29C                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F29E                 anda    #7              ; ignore play forward flag
ROM:F2A0                 cmpa    <DQ_MFD         ; manchester field in decimal as read
ROM:F2A2                 bne     FrameMismatch   ; wrong frame was read
ROM:F2A4                 ldd     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F2A4                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F2A6                 cmpd    <DQ_MFD+1       ; manchester field in decimal as read
ROM:F2A9                 beq     FrameMatch
ROM:F2AB
ROM:F2AB FrameMismatch:                          ; CODE XREF: DISK1+6A j
ROM:F2AB                 ldb     #$62 ; 'b'
ROM:F2AD                 jsr     NWMESS          ; display "WRONG FRAME"
ROM:F2B0
ROM:F2B0 OnError:                                ; CODE XREF: DISK1+45 j
ROM:F2B0                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F2B0                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F2B2                 bpl     Disc1Return     ; branch if not playing forward
ROM:F2B4                 lda     <RL_PAS
ROM:F2B6                 cmpa    #$40 ; '@'
ROM:F2B8                 bcs     Disc1Return     ; branch if skipping backward
ROM:F2BA                 cmpa    #$C0 ; '+'
ROM:F2BC                 bhi     Disc1Return     ; branch if skipping forward
ROM:F2BE                 lda     <RL_CNM
ROM:F2C0                 cmpa    #$C0 ; '+'
ROM:F2C2                 lbcs    OnJoystickCenteredFreeze ; branch if not playing forward-keep trying
ROM:F2C6                 lbra    OnPlaying       ; branch in case the disk is bad here
ROM:F2C9 ; ---------------------------------------------------------------------------
ROM:F2C9
ROM:F2C9 FrameMatch:                             ; CODE XREF: DISK1+71 j
ROM:F2C9                 ldb     #$62 ; 'b'
ROM:F2CB                 jsr     UNMESS          ; erase "WRONG FRAME" message
ROM:F2CE                 lda     <RL_PAS
ROM:F2D0                 cmpa    #$60 ; '`'
ROM:F2D2                 bcc     OnJoystickNotLeft ; branch if we are not jumping backward
ROM:F2D4                 ldb     NVRAMZ_R_BYT0   ; switches are active low
ROM:F2D7                 andb    #IN0_LeftThumb
ROM:F2D9                 bne     PostLeftButtonCheck ; "BACK"
ROM:F2DB                 lda     JMPBCK          ; 0 = going forward, non-zero = going backward
ROM:F2DE                 bne     $22             ; branch if last frame we jumped backward, SO GO FORWARD THIS TIME
ROM:F2E0
ROM:F2E0 $32JumpBack:                            ; CODE XREF: DISK1+60 J
ROM:F2E0                                         ; DISK1+E5 j
ROM:F2E0                 lda     #$20 ; ' '      ; set joystick for no change
ROM:F2E2
ROM:F2E2 PostLeftButtonCheck:                    ; CODE XREF: DISK1+A1 j
ROM:F2E2                 ldb     #$5F ; '_'      ; "BACK"
ROM:F2E4                 stb     JMPBCK          ; flag jump backward (NON-ZERO)
ROM:F2E7                 cmpa    #$20 ; ' '
ROM:F2E9                 bcs     $25AccelJmpCounter ; branch if we need to accelerate the jump counter
ROM:F2EB                 cmpa    #$40 ; '@'
ROM:F2ED                 bcs     PositionDisc    ; branch if we aren't changing the jump count
ROM:F2EF
ROM:F2EF DecJMPCT:                               ; CODE XREF: DISK1+F0 j
ROM:F2EF                 lda     JMPCT+1
ROM:F2F2                 adda    #$99 ; 'Ö'      ; -1 decimal
ROM:F2F4                 daa
ROM:F2F5                 sta     JMPCT+1
ROM:F2F8                 lda     JMPCT
ROM:F2FB                 adca    #$99 ; 'Ö'
ROM:F2FD                 daa
ROM:F2FE                 bcs     PostUnderflowAdjust ; branch if we haven't underflowed
ROM:F300                 lda     #1
ROM:F302                 sta     JMPCT+1
ROM:F305                 lda     #0
ROM:F307
ROM:F307 PostUnderflowAdjust:                    ; CODE XREF: DISK1+C6 j
ROM:F307                 sta     JMPCT
ROM:F30A
ROM:F30A PositionDisc:                           ; CODE XREF: DISK1+B5 j
ROM:F30A                 jsr     DISKP           ; Change desired frame no matter what which will cause a skip.
ROM:F30A                                         ; If jump count is zero, it will be adjusted to 1.
ROM:F30D                 bra     DisplayCurOp    ; display current operation
ROM:F30F ; ---------------------------------------------------------------------------
ROM:F30F
ROM:F30F OnJoystickNotLeft:                      ; CODE XREF: DISK1+9A j
ROM:F30F                 cmpa    #$A0 ; 'á'
ROM:F311                 bls     OnJoystickNotRight
ROM:F313                 ldb     NVRAMZ_R_BYT0   ; switches are active low
ROM:F316                 andb    #IN0_LeftThumb
ROM:F318                 bne     PostLeftThumbCheck ; branch if button is not pressed
ROM:F31A                 lda     JMPBCK          ; 0 = going forward, non-zero = going backward
ROM:F31D                 beq     $32JumpBack     ; last frame we jumped forward, so go backward this time
ROM:F31F
ROM:F31F $22:                                    ; CODE XREF: DISK1+A6 j
ROM:F31F                 lda     #$D0 ; '-'      ; set joystick for no change
ROM:F321
ROM:F321 PostLeftThumbCheck:                     ; CODE XREF: DISK1+E0 j
ROM:F321                 ldb     #$60 ; '`'      ; "FORWARD"
ROM:F323                 clr     JMPBCK          ; flag jumping forward
ROM:F326                 cmpa    #$C0 ; '+'
ROM:F328                 bcs     DecJMPCT
ROM:F32A                 cmpa    #$E0 ; 'a'
ROM:F32C                 bcs     OnNoJmpChange   ; add/subtract "JMPCT" to current disk position
ROM:F32E
ROM:F32E $25AccelJmpCounter:                     ; CODE XREF: DISK1+B1 j
ROM:F32E                 lda     JMPCT+1
ROM:F331                 lsra
ROM:F332                 lsra
ROM:F333                 lsra
ROM:F334                 lsra                    ; add ten's digit to one's digit (pseudo speedup)
ROM:F335                 orcc    #1
ROM:F337                 adca    JMPCT+1
ROM:F33A                 daa
ROM:F33B                 sta     JMPCT+1
ROM:F33E                 lda     JMPCT
ROM:F341                 adca    #0
ROM:F343                 daa
ROM:F344                 bcc     loc_F34B
ROM:F346                 lda     #$99 ; 'Ö'
ROM:F348                 sta     JMPCT+1
ROM:F34B
ROM:F34B loc_F34B:                               ; CODE XREF: DISK1+10C j
ROM:F34B                 sta     JMPCT
ROM:F34E
ROM:F34E OnNoJmpChange:                          ; CODE XREF: DISK1+F4 j
ROM:F34E                 jsr     DISKP           ; add/subtract "JMPCT" to current disk position
ROM:F351                 bra     DisplayCurOp    ; display current operation
ROM:F353 ; ---------------------------------------------------------------------------
ROM:F353
ROM:F353 OnJoystickNotRight:                     ; CODE XREF: DISK1+D9 j
ROM:F353                 lda     <RL_CNM
ROM:F355                 cmpa    #$C0 ; '+'
ROM:F357                 bcs     OnJoystickCenteredFreeze
ROM:F359
ROM:F359 OnPlaying:                              ; CODE XREF: DISK1+8E j
ROM:F359                 ldd     #0              ; clear jmp counter
ROM:F35C                 std     JMPCT
ROM:F35F                 lda     FLDSYNC         ; sync with disk, 0 for F, !=0 for alternate field
ROM:F362                 beq     ShowPlayingMessage ; branch if we're on the first field (the current frame number will not change this field)
ROM:F364                 ldd     #1              ; the current frame number will advance by 1 on the next field,
ROM:F364                                         ; so adjust our desired frame number accordingly so that we don't send any jumps
ROM:F367                 std     JMPCT
ROM:F36A                 jsr     DISKA           ; decimal add of JMPCT (16-bit word) to DQ_GMD
ROM:F36D
ROM:F36D ShowPlayingMessage:                     ; CODE XREF: DISK1+12A j
ROM:F36D                 ldb     #$5E ; '^'      ; "PLAY"
ROM:F36F                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F36F                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F371                 oraa    #$80 ; 'Ç'
ROM:F373                 sta     <DQ_GMD         ; set play forward flag
ROM:F375                 bra     DisplayCurOp    ; display current operation
ROM:F377 ; ---------------------------------------------------------------------------
ROM:F377
ROM:F377 OnJoystickCenteredFreeze:               ; CODE XREF: DISK1+8A j
ROM:F377                                         ; DISK1+11F j
ROM:F377                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F377                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F379                 anda    #7
ROM:F37B                 sta     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F37B                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F37D                 ldd     #0
ROM:F380                 std     JMPCT           ; clear jmp counter
ROM:F383                 ldb     #$5D ; ']'      ; "FREEZE"
ROM:F385
ROM:F385 DisplayCurOp:                           ; CODE XREF: DISK1+D5 j
ROM:F385                                         ; DISK1+119 j ...
ROM:F385                 jsr     NWMESS          ; display current operation
ROM:F388                 leay    2,y             ; space over to the right
ROM:F38A                 ldu     #JMPCT
ROM:F38D                 jmp     VW4DIG          ; display 4 digits
ROM:F38D ; End of function DISK1
ROM:F38D
ROM:F390
ROM:F390 ; =============== S U B R O U T I N E =======================================
ROM:F390
ROM:F390 ; Change desired frame no matter what which will cause a skip.
ROM:F390 ; If jump count is zero, it will be adjusted to 1.
ROM:F390
ROM:F390 DISKP:                                  ; CODE XREF: DISK1:PositionDisc P
ROM:F390                                         ; DISK1:OnNoJmpChange P
ROM:F390                 lda     JMPCT
ROM:F393                 oraa    JMPCT+1
ROM:F396                 bne     AdjustDesiredFrame
ROM:F398                 inc     JMPCT+1         ; counter should always be non-zero
ROM:F39B
ROM:F39B AdjustDesiredFrame:                     ; CODE XREF: DISKP+6 j
ROM:F39B                 lda     JMPBCK          ; 0 = going forward, non-zero = going backward
ROM:F39E                 beq     DISKA           ; branch if we are NOT jumping backward
ROM:F3A0                 pshs    b
ROM:F3A2                 ldd     #$9999
ROM:F3A5                 subd    JMPCT
ROM:F3A8                 sta     <TTEMP          ; 1's complement
ROM:F3AA                 tfr     b, a
ROM:F3AC                 puls    b
ROM:F3AE                 orcc    #1              ; add 1 for 2's complement
ROM:F3B0                 adca    <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3B0                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3B2                 daa
ROM:F3B3                 sta     <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3B3                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3B5                 lda     <TTEMP
ROM:F3B7                 adca    <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3B7                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3B9                 daa
ROM:F3BA                 sta     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3BA                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3BC                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F3BC                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3BE                 adca    #$99 ; 'Ö'
ROM:F3C0                 daa
ROM:F3C1                 anda    #7              ; clear play forward flag
ROM:F3C3                 sta     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F3C3                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3C5                 bcc     ResetToFrame1   ; branch if we've underflowed, reset to frame 1
ROM:F3C7                 oraa    <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3C7                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3C9                 oraa    <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3C9                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3CB                 bne     Done            ; branch if the new frame is not 000000
ROM:F3CD
ROM:F3CD ResetToFrame1:                          ; CODE XREF: DISKP+35 j
ROM:F3CD                 lda     #0              ; frame 0 is not allowed, reset to frame 1
ROM:F3CF                 sta     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F3CF                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3D1                 sta     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3D1                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3D3                 lda     #1
ROM:F3D5                 sta     <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3D5                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3D7
ROM:F3D7 Done:                                   ; CODE XREF: DISKP+3B j
ROM:F3D7                 bra     DISKP_DONE
ROM:F3D7 ; End of function DISKP
ROM:F3D7
ROM:F3D9
ROM:F3D9 ; =============== S U B R O U T I N E =======================================
ROM:F3D9
ROM:F3D9 ; decimal add of JMPCT (16-bit word) to DQ_GMD
ROM:F3D9
ROM:F3D9 DISKA:                                  ; CODE XREF: DISK1+132 P
ROM:F3D9                                         ; DISKP+E j
ROM:F3D9                 lda     JMPCT+1
ROM:F3DC                 adda    <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3DC                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3DE                 daa
ROM:F3DF                 sta     <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:F3DF                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3E1                 lda     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3E1                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3E3                 adca    JMPCT
ROM:F3E6                 daa
ROM:F3E7                 sta     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:F3E7                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3E9                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F3E9                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3EB                 adca    #0
ROM:F3ED                 anda    #7              ; clear play forward flag
ROM:F3EF                 sta     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:F3EF                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:F3F1
ROM:F3F1 DISKP_DONE:                             ; CODE XREF: DISKP:Done j
ROM:F3F1                 rts
ROM:F3F1 ; End of function DISKA

I am not going to go into great detail explaining the disassembly as I've included plenty of comments.  A few notes are that the left and right thumb buttons can do stuff, and then moving the controller vertically while the disc is paused (ie "frozen") will cause the disc to play forward at 1X.  Both of these are undocumented as far as I can tell.

Also of note is that this disc test is relatively simple despite appearing quite complex at first glance.  While the test is active, it gets called every single field and does two things:
- checks to see if the disc is on the frame number that it expects for the current field
- tells the game program what laserdisc frame is wants to be on on the next field

If the first check fails, it prints an "FR" error message on the screen.  Otherwise, it just reports the desired frame and actual frame which normally will match.

My conclusion at this point was that I was not going to find my answer by studying the disc test, but would need to study the main disc routines elsewhere in the code.

After more digging, I came across the function that actually sends new commands to the laserdisc player (warning, LONG!):

ROM:E650 ; =============== S U B R O U T I N E =======================================
ROM:E650
ROM:E650 ; create commands, output to disc
ROM:E650
ROM:E650 DODKWR:                                 ; CODE XREF: DODK+35 P
ROM:E650                                         ; ROM:EE6F P
ROM:E650                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E650                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E652                 bpl     OnHighBitClear
ROM:E654                 jsr     GenPlayCmd
ROM:E657                 bra     AfterCmdCreated
ROM:E659 ; ---------------------------------------------------------------------------
ROM:E659
ROM:E659 OnHighBitClear:                         ; CODE XREF: DODKWR+2 j
ROM:E659                 lda     <DQ_LED         ; lead in if >0, lead out if >=80
ROM:E65B                 beq     OnNoLeadInOrLeadOut
ROM:E65D                 bmi     loc_E664
ROM:E65F                 jsr     sub_E751
ROM:E662                 bra     loc_E667
ROM:E664 ; ---------------------------------------------------------------------------
ROM:E664
ROM:E664 loc_E664:                               ; CODE XREF: DODKWR+D j
ROM:E664                 jsr     sub_E756
ROM:E667
ROM:E667 loc_E667:                               ; CODE XREF: DODKWR+12 j
ROM:E667                 bra     AfterCmdCreated
ROM:E669 ; ---------------------------------------------------------------------------
ROM:E669
ROM:E669 OnNoLeadInOrLeadOut:                    ; CODE XREF: DODKWR+B j
ROM:E669                 lda     <FLDSYNC        ; sync with disk, 0 for F, !=0 for alternate field
ROM:E66B                 bne     OnAltField
ROM:E66D                 jsr     CreateCmdIfOnFirstField
ROM:E670                 bra     AfterCmdCreated
ROM:E672 ; ---------------------------------------------------------------------------
ROM:E672
ROM:E672 OnAltField:                             ; CODE XREF: DODKWR+1B j
ROM:E672                 jsr     CreateCmdIfOnAltField
ROM:E675
ROM:E675 AfterCmdCreated:                        ; CODE XREF: DODKWR+7 j
ROM:E675                                         ; DODKWR:loc_E667 j ...
ROM:E675                 orcc    #$40 ; '@'
ROM:E677                 inc     <DQ_WTO         ; write timeout counter
ROM:E679                 ldx     #DQ_CMD         ; output command buffer
ROM:E67C
ROM:E67C Send1CmdByte:                           ; CODE XREF: DODKWR+51 j
ROM:E67C                 sta     W_RDED          ; writing to this location lowers RDEN' (enables it)
ROM:E67F                 lda     R_DATD          ; reading from this location grabs the data from the VP-931 and disabled the RDEN' line (raises it)
ROM:E682                 lda     ,x+             ; load from buffer
ROM:E684                 sta     DSKLATCH        ; the data stored in this latch will be sent to the LDP
ROM:E687                 ldd     #$FF
ROM:E68A                 sta     WRDSK           ; 5 cycles (WREN' low)
ROM:E68D                 stb     WRDSK           ; 5 cycles (WREN' low)
ROM:E690                 ldb     #$10            ; timeout value
ROM:E692
ROM:E692 WhileDakIsLow:                          ; CODE XREF: DODKWR+4A j
ROM:E692                 lda     R_BYT2_R_DAVD   ; bit 7: DAV'
ROM:E692                                         ; bit 6: DAK
ROM:E692                                         ; bit 5: OPRT'
ROM:E692                                         ; bits 0-4 : unconnected
ROM:E695                 anda    #$40 ; '@'      ; isolate DAK
ROM:E697                 bne     DakIsHigh       ; have we sent the three command bytes?
ROM:E699                 decb
ROM:E69A                 bgt     WhileDakIsLow
ROM:E69C                 bra     PostWriteTimeoutCounterClear ; we timed out, don't clear the write timeout counter,
ROM:E69C                                         ; or send any more command bytes
ROM:E69E ; ---------------------------------------------------------------------------
ROM:E69E
ROM:E69E DakIsHigh:                              ; CODE XREF: DODKWR+47 j
ROM:E69E                 cmpx    #DQ_MFD         ; have we sent the three command bytes?
ROM:E6A1                 bcs     Send1CmdByte
ROM:E6A3                 clr     <DQ_WTO         ; reset write timeout counter
ROM:E6A5
ROM:E6A5 PostWriteTimeoutCounterClear:           ; CODE XREF: DODKWR+4C j
ROM:E6A5                 sta     W_RDED          ; writing to this location lowers RDEN' (enables it)
ROM:E6A8                 lda     R_DATD          ; reading from this location grabs the data from the VP-931 and disabled the RDEN' line (raises it)
ROM:E6AB                 sta     W_FRQCLR        ; writing here clears FIRQ, allows new FIRQ
ROM:E6AE                 andcc   #$BF ; '+'
ROM:E6B0                 lda     <DQ_RTO         ; read timeout counter
ROM:E6B2                 oraa    <DQ_WTO         ; write timeout counter
ROM:E6B4                 anda    #$80 ; 'Ç'
ROM:E6B6                 beq     DontResetLDP    ; branch if we don't have enough errors to reset the VP-931
ROM:E6B8                 inc     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E6BA                 lda     #0
ROM:E6BC                 sta     DSKRST          ; reset the LDP
ROM:E6BF                 clr     <DQ_RTO         ; read timeout counter
ROM:E6C1                 clr     <DQ_WTO         ; write timeout counter
ROM:E6C3                 bra     PostResetDecision
ROM:E6C5 ; ---------------------------------------------------------------------------
ROM:E6C5
ROM:E6C5 DontResetLDP:                           ; CODE XREF: DODKWR+66 j
ROM:E6C5                 lda     #$FF
ROM:E6C7                 sta     DSKRST          ; aka W.RSTD
ROM:E6C7                                         ; disk reset (active low) (at least 2 ms)
ROM:E6CA
ROM:E6CA PostResetDecision:                      ; CODE XREF: DODKWR+73 j
ROM:E6CA                 lda     R_BYT2_R_DAVD   ; bit 7: DAV'
ROM:E6CA                                         ; bit 6: DAK
ROM:E6CA                                         ; bit 5: OPRT'
ROM:E6CA                                         ; bits 0-4 : unconnected
ROM:E6CD                 anda    #$20 ; ' '      ; isolate OPRT'
ROM:E6CF                 beq     loc_E6D3        ; branch if VP-931 is plugged in
ROM:E6D1                 clr     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E6D3
ROM:E6D3 loc_E6D3:                               ; CODE XREF: DODKWR+7F j
ROM:E6D3                 lda     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E6D5                 bne     locret_E6DC
ROM:E6D7                 lda     #$FF
ROM:E6D9                 sta     DSKLCK_W_LCKV   ; 0: lock to disk, 1: self lock
ROM:E6DC
ROM:E6DC locret_E6DC:                            ; CODE XREF: DODKWR+85 j
ROM:E6DC                 rts
ROM:E6DC ; End of function DODKWR
ROM:E6DC
ROM:E6DD
ROM:E6DD ; =============== S U B R O U T I N E =======================================
ROM:E6DD
ROM:E6DD
ROM:E6DD CreateCmdIfOnFirstField:                ; CODE XREF: DODKWR+1D P
ROM:E6DD
ROM:E6DD ; FUNCTION CHUNK AT ROM:E75B SIZE 00000002 BYTES
ROM:E6DD ; FUNCTION CHUNK AT ROM:E765 SIZE 0000000C BYTES
ROM:E6DD ; FUNCTION CHUNK AT ROM:E7B0 SIZE 0000001C BYTES
ROM:E6DD
ROM:E6DD                 ldd     <DQ_MFD+1       ; manchester field in decimal as read
ROM:E6DF                 subd    <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:E6DF                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E6E1                 bne     CanWeGenPause
ROM:E6E3                 lda     <DQ_MFD         ; manchester field in decimal as read
ROM:E6E5                 suba    <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E6E5                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E6E7                 bne     CanWeGenPause
ROM:E6E9                 jmp     GenPlayCmd
ROM:E6EC ; ---------------------------------------------------------------------------
ROM:E6EC
ROM:E6EC CanWeGenPause:                          ; CODE XREF: CreateCmdIfOnFirstField+4 j
ROM:E6EC                                         ; CreateCmdIfOnFirstField+A j
ROM:E6EC                 lda     <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:E6EC                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E6EE                 adda    #1              ; add 1 to see what would happen if we jumped back 1 track (ie paused)
ROM:E6F0                 daa
ROM:E6F1                 eora    <DQ_MFD+2       ; manchester field in decimal as read
ROM:E6F3                 bne     PauseWontWork
ROM:E6F5                 lda     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:E6F5                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E6F7                 adca    #0
ROM:E6F9                 daa
ROM:E6FA                 eora    <DQ_MFD+1       ; manchester field in decimal as read
ROM:E6FC                 bne     PauseWontWork
ROM:E6FE                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E6FE                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E700                 adca    #0
ROM:E702                 daa
ROM:E703                 eora    <DQ_MFD         ; manchester field in decimal as read
ROM:E705                 bne     PauseWontWork
ROM:E707                 jmp     GenPauseCmd     ; stay where we are (jump back 1 track to do a pause)
ROM:E70A ; ---------------------------------------------------------------------------
ROM:E70A
ROM:E70A PauseWontWork:                          ; CODE XREF: CreateCmdIfOnFirstField+16 j
ROM:E70A                                         ; CreateCmdIfOnFirstField+1F j ...
ROM:E70A                 lda     <DQ_FTO         ; first field (F) timeout
ROM:E70C                 beq     OnNoFieldTimeouts
ROM:E70E                 jmp     ClearDiscLockThenGenPlay
ROM:E711 ; ---------------------------------------------------------------------------
ROM:E711
ROM:E711 OnNoFieldTimeouts:                      ; CODE XREF: CreateCmdIfOnFirstField+2F j
ROM:E711                 jmp     CreateSearchAndPlayCmdAdd1 ; Creates a SEARCH+PLAY command (prefix 0xF...)
ROM:E711 ; End of function CreateCmdIfOnFirstField ; to the game requested frame #,
ROM:E711                                         ; but adds one to the request
ROM:E711                                         ; (so if the request frame were 36128, it would do a jump to 36129)
ROM:E714 ; ---------------------------------------------------------------------------
ROM:E714                 jmp     GenSeekAndPlayCmd ; create SEEK+PLAY command that jumps to the frame we want to be on
ROM:E717
ROM:E717 ; =============== S U B R O U T I N E =======================================
ROM:E717
ROM:E717
ROM:E717 CreateCmdIfOnAltField:                  ; CODE XREF: DODKWR:OnAltField P
ROM:E717
ROM:E717 ; FUNCTION CHUNK AT ROM:E785 SIZE 0000000F BYTES
ROM:E717
ROM:E717                 ldd     <DQ_MFD+1       ; manchester field in decimal as read
ROM:E719                 subd    <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:E719                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E71B                 bne     DesiredAndActualPicNumsMismatch
ROM:E71D                 lda     <DQ_MFD         ; manchester field in decimal as read
ROM:E71F                 suba    <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E71F                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E721                 bne     DesiredAndActualPicNumsMismatch
ROM:E723                 jmp     GenPauseCmd     ; stay where we are (jump back 1 track to do a pause)
ROM:E726 ; ---------------------------------------------------------------------------
ROM:E726
ROM:E726 DesiredAndActualPicNumsMismatch:        ; CODE XREF: CreateCmdIfOnAltField+4 j
ROM:E726                                         ; CreateCmdIfOnAltField+A j
ROM:E726                 lda     <DQ_MFD+2       ; manchester field in decimal as read
ROM:E728                 adda    #1              ; add 1 to see what would happen if we just let the disc play
ROM:E728                                         ; (the picture number will increment on the next field)
ROM:E72A                 daa
ROM:E72B                 eora    <DQ_GMD+2       ; "Games Requested Frame #" converted to decimal
ROM:E72B                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E72D                 bne     JustPlayingIsNotGoodEnough ; if we won't get to where we need to be by just letting the disc play
ROM:E72F                 lda     <DQ_MFD+1       ; manchester field in decimal as read
ROM:E731                 adca    #0
ROM:E733                 daa
ROM:E734                 eora    <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:E734                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E736                 bne     JustPlayingIsNotGoodEnough ; if we won't get to where we need to be by just letting the disc play
ROM:E738                 lda     <DQ_MFD         ; manchester field in decimal as read
ROM:E73A                 adca    #0
ROM:E73C                 daa
ROM:E73D                 eora    <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E73D                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E73F                 bne     JustPlayingIsNotGoodEnough ; if we won't get to where we need to be by just letting the disc play
ROM:E741                 jmp     GenPlayCmd
ROM:E744 ; ---------------------------------------------------------------------------
ROM:E744
ROM:E744 JustPlayingIsNotGoodEnough:             ; CODE XREF: CreateCmdIfOnAltField+16 j
ROM:E744                                         ; CreateCmdIfOnAltField+1F j ...
ROM:E744                 lda     <DQ_ATO         ; if we won't get to where we need to be by just letting the disc play
ROM:E746                 beq     JmpGenSeekAndPlayCmd
ROM:E748                 jmp     ClearDiscLockThenGenPlay
ROM:E74B ; ---------------------------------------------------------------------------
ROM:E74B
ROM:E74B JmpGenSeekAndPlayCmd:                   ; CODE XREF: CreateCmdIfOnAltField+2F j
ROM:E74B                 jmp     GenSeekAndPlayCmd ; create SEEK+PLAY command that jumps to the frame we want to be on
ROM:E74B ; End of function CreateCmdIfOnAltField
ROM:E74B
ROM:E74B ; ---------------------------------------------------------------------------
ROM:E74E                 fcb $7E ; ~
ROM:E74F                 fcb $E7 ; t
ROM:E750                 fcb $94 ; ö
ROM:E751
ROM:E751 ; =============== S U B R O U T I N E =======================================
ROM:E751
ROM:E751
ROM:E751 sub_E751:                               ; CODE XREF: DODKWR+F P
ROM:E751
ROM:E751 ; FUNCTION CHUNK AT ROM:E771 SIZE 0000000A BYTES
ROM:E751
ROM:E751                 ldb     #$90 ; 'É'
ROM:E753                 jmp     loc_E771
ROM:E753 ; End of function sub_E751
ROM:E753
ROM:E756
ROM:E756 ; =============== S U B R O U T I N E =======================================
ROM:E756
ROM:E756
ROM:E756 sub_E756:                               ; CODE XREF: DODKWR:loc_E664 P
ROM:E756
ROM:E756 ; FUNCTION CHUNK AT ROM:E77B SIZE 0000000A BYTES
ROM:E756
ROM:E756                 ldb     #$90 ; 'É'
ROM:E758                 jmp     loc_E77B
ROM:E758 ; End of function sub_E756
ROM:E758
ROM:E75B ; ---------------------------------------------------------------------------
ROM:E75B ; START OF FUNCTION CHUNK FOR CreateCmdIfOnFirstField
ROM:E75B
ROM:E75B ClearDiscLockThenGenPlay:               ; CODE XREF: CreateCmdIfOnFirstField+31 J
ROM:E75B                                         ; CreateCmdIfOnAltField+31 J
ROM:E75B                 clr     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E75B ; END OF FUNCTION CHUNK FOR CreateCmdIfOnFirstField
ROM:E75D
ROM:E75D ; =============== S U B R O U T I N E =======================================
ROM:E75D
ROM:E75D
ROM:E75D GenPlayCmd:                             ; CODE XREF: DODKWR+4 P
ROM:E75D                                         ; CreateCmdIfOnFirstField+C J ...
ROM:E75D                 ldd     #0
ROM:E760                 std     <DQ_CMD+1       ; output command buffer
ROM:E762                 sta     <DQ_CMD         ; output command buffer
ROM:E764                 rts
ROM:E764 ; End of function GenPlayCmd
ROM:E764
ROM:E765 ; ---------------------------------------------------------------------------
ROM:E765 ; START OF FUNCTION CHUNK FOR CreateCmdIfOnFirstField
ROM:E765
ROM:E765 GenPauseCmd:                            ; CODE XREF: CreateCmdIfOnFirstField+2A J
ROM:E765                                         ; CreateCmdIfOnAltField+C J
ROM:E765                 lda     <DQ_LCK         ; stay where we are (jump back 1 track to do a pause)
ROM:E767                 ldd     #$F001
ROM:E76A                 std     <DQ_CMD+1       ; output command buffer
ROM:E76C                 lda     #0
ROM:E76E                 sta     <DQ_CMD         ; output command buffer
ROM:E770                 rts
ROM:E770 ; END OF FUNCTION CHUNK FOR CreateCmdIfOnFirstField
ROM:E771 ; ---------------------------------------------------------------------------
ROM:E771 ; START OF FUNCTION CHUNK FOR sub_E751
ROM:E771
ROM:E771 loc_E771:                               ; CODE XREF: sub_E751+2 J
ROM:E771                 clr     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E773                 stb     <DQ_CMD+2       ; output command buffer
ROM:E775                 ldd     #$E0 ; 'a'
ROM:E778                 std     <DQ_CMD         ; output command buffer
ROM:E77A                 rts
ROM:E77A ; END OF FUNCTION CHUNK FOR sub_E751
ROM:E77B ; ---------------------------------------------------------------------------
ROM:E77B ; START OF FUNCTION CHUNK FOR sub_E756
ROM:E77B
ROM:E77B loc_E77B:                               ; CODE XREF: sub_E756+2 J
ROM:E77B                 clr     <DQ_LCK         ; 0 = cannot lock to disc, 1 = can lock to disc
ROM:E77D                 stb     <DQ_CMD+2       ; output command buffer
ROM:E77F                 ldd     #$F0 ; '='
ROM:E782                 std     <DQ_CMD         ; output command buffer
ROM:E784                 rts
ROM:E784 ; END OF FUNCTION CHUNK FOR sub_E756
ROM:E785 ; ---------------------------------------------------------------------------
ROM:E785 ; START OF FUNCTION CHUNK FOR CreateCmdIfOnAltField
ROM:E785
ROM:E785 GenSeekAndPlayCmd:                      ; CODE XREF: ROM:E714 J
ROM:E785                                         ; CreateCmdIfOnAltField:JmpGenSeekAndPlayCmd J
ROM:E785                 clr     <DQ_LCK         ; create SEEK+PLAY command that jumps to the frame we want to be on
ROM:E787                 ldd     <DQ_GMD+1       ; "Games Requested Frame #" converted to decimal
ROM:E787                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E789                 std     <DQ_CMD+1       ; output command buffer
ROM:E78B                 lda     <DQ_GMD         ; "Games Requested Frame #" converted to decimal
ROM:E78B                                         ; The high bit of the first byte will be 1 if the disc is supposed to just be playing forward at 1X. (see E650 and F373)
ROM:E78D                 anda    #7
ROM:E78F                 oraa    #$F0 ; '='
ROM:E791                 sta     <DQ_CMD         ; output command buffer
ROM:E793                 rts
ROM:E793 ; END OF FUNCTION CHUNK FOR CreateCmdIfOnAltField
ROM:E793 ; ---------------------------------------------------------------------------

This isn't completely disassembled, but I learned a lot of useful info by studying this.

DODKWR ($E650) will just send a play command if the highest bit of DQ_CMD is set.
Otherwise, if disc is on the 'first field' of a laserdisc frame, go to CreateCmdIfOnFirstField ($E6DD).
Otherwise, if disc is on the 'alternate field' of a laserdisc frame, so go to CreateCmdIfOnAltField ($E717).
After the command is created, it sends the command bytes to the VP931 ($E675).
The difference between what program does on the first field vs the alternate field is pretty interesting:
- If the requested target field that program needs to seek to is the same frame number that the disc is already on,
-- AND If disc is on the first field, then a play command will be sent for the current field ($E6E9).
-- Else the disc is on the alternate field, so a jump back 1 track command will be sent ($E765).  This is a fancy way of just pausing the disc.

- If the requested target field that the program needs to seek to is different from the current frame number that the disc is on,
-- AND if the disc is on the first field, then the program will issue a jump command to the target frame plus 1 (CreateSearchAndPlayCmdAdd1, $E7B0) which is pretty interesting.  More on this later.
-- Else the disc is on the alternate field, so the program will issue a jump command to the target frame ($E785).

Why does the Firefox program add 1 to the target frame if jumping from the first field of a frame?

Each frame is composed of two fields.  So no matter what, the next field that will be displayed MUST be an alternate field.  A jump to a frame number implicitly always targets the first field of a frame.  Therefore, a jump that is initiated on the first field of a frame cannot complete until the field after the next field (which is an alternate field).

The VP931's behavior in this case is to land one field before the target field.  So, for example, if I was on the first field of frame 1 and I requested a jump to frame 10, the VP931 would land me on the alternate field of frame 9 first, and then take me to the first field of frame 10 next.  The Firefox program knows this, so it compensates by adding 1 to the desired target frame so that instead of landing 1 frame ahead of the target (and potentially show the wrong field of the wrong frame), we land in the middle of our target frame.  Or, in our example, if we were on the first field of frame 1, and we wanted to land inside frame 10, we would issue a jump to frame 11 and the VP931 would land us inside of the alternate field of frame 10.  It's kind of a quirky behavior that I didn't understand until I really dug into it.  Now Dexter (with the latest firmware update as of today) will properly exhibit this behavior and Firefox's disc test passes with flying colors.