Tuesday, October 14, 2014

Dragon's Lair ticks

I haven't posted a blog entry in a while and this is something pretty interesting that I'd like to have saved somewhere so here goes.

Dragon's Lair's scene data has a concept of 'ticks' which roughly map to clock ticks generated by the hardware.  They are used to determine how long scene segments should last.

One issue with Dragon's Lair hardware is that the clock tick frequency differs slightly across different revisions of the hardware.  Dragon's Lair's ROM program does a speed test upon boot-up to determine whether the hardware clock is faster, normal, or slower.  The result of this speed test is displayed in the scoreboard as a 1 (faster), 2 (normal), or 3 (slower).

Here is the disassembled code that documents this speed test:

ROM:11BA SpeedTestLoop:                          ; CODE XREF: START+7B j
ROM:11BA                                         ; START+88 j ...
ROM:11BA                 ld      hl, SpeedTest_A001 ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11BD                 bit     1, (hl)
ROM:11BF                 jr      nz, SpeedTestGotIrq ; if this is non-zero, it means IRQ has been triggered
ROM:11C1                 inc     bc
ROM:11C2                 jr      SpeedTestLoop   ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11C4 ; ---------------------------------------------------------------------------
ROM:11C4
ROM:11C4 SpeedTestGotIrq:                        ; CODE XREF: START+78 j
ROM:11C4                 res     1, (hl)         ; clear IRQ flag
ROM:11C6                 inc     d
ROM:11C7                 ld      a, 48           ; don't start tracking IRQs until we've got 48 of them
ROM:11C9                 cp      d
ROM:11CA                 jr      c, loc_11D1
ROM:11CC                 ld      bc, 0
ROM:11CF                 jr      SpeedTestLoop   ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D1 ; ---------------------------------------------------------------------------
ROM:11D1
ROM:11D1 loc_11D1:                               ; CODE XREF: START+83 j
ROM:11D1                 ld      a, 50
ROM:11D3                 cp      d
ROM:11D4                 jr      nz, SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D6                 ld      a, c
ROM:11D7                 ld      (unk_E035), a   ; least significant digit from BC
ROM:11DA                 srl     a
ROM:11DC                 srl     a
ROM:11DE                 srl     a
ROM:11E0                 srl     a
ROM:11E2                 ld      (unk_E034), a
ROM:11E5                 ld      a, b
ROM:11E6                 ld      (unk_E033), a
ROM:11E9                 srl     a
ROM:11EB                 srl     a
ROM:11ED                 srl     a
ROM:11EF                 srl     a
ROM:11F1                 ld      (unk_E032), a   ; most significant digit from BC
ROM:11F4                 ld      a, 0Ah
ROM:11F6                 cp      b
ROM:11F7                 jr      c, BcGreaterThanOrEq0A00 ; if BC >= 0x0A00 then branch
ROM:11F9                 set     3, (hl)         ; set bit 3 of A001 to indicate that speed test's result is 'faster'
ROM:11FB                 ld      a, 1            ; speed test got a '1' result
ROM:11FD                 jr      SpeedTest1Or2Result ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:11FF ; ---------------------------------------------------------------------------
ROM:11FF
ROM:11FF BcGreaterThanOrEq0A00:                  ; CODE XREF: START+B0 j
ROM:11FF                 ld      a, 15h          ; check to see if BC >= 0x1500
ROM:1201                 cp      b
ROM:1202                 jr      c, SpeedTest3Result ; branch if BC >= 0x1500
ROM:1204                 ld      a, 2            ; speed test got a '2' result
ROM:1206
ROM:1206 SpeedTest1Or2Result:                    ; CODE XREF: START+B6 j
ROM:1206                 set     0, (hl)         ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:1208
ROM:1208 ExitSpeedTest:                          ; CODE XREF: START+CC j
ROM:1208                 ld      (unk_E038), a   ; set speed test result to scoreboard
ROM:120B                 set     2, (hl)         ; set bit 2 of A001 to indicate that the speed test is finished
ROM:120D                 jr      BootPhase2      ; play 'move' beep
ROM:120F ; ---------------------------------------------------------------------------
ROM:120F
ROM:120F SpeedTest3Result:                       ; CODE XREF: START+BB j
ROM:120F                 res     0, (hl)         ; clear bit 0 to indicate that speed test result is slower
ROM:1211                 ld      a, 3            ; display a 3 on scoreboard
ROM:1213                 jr      ExitSpeedTest   ; set speed test result to scoreboard
ROM:1215 ; ---------------------------------------------------------------------------


So what does the result of the speed test mean for the ticks?

If the speed test got a 1 (faster) or a 2 (normal), then every 31st tick is dropped.  If the speed test got a 3 (slower) then every 255th tick is dropped.  Here is the code which handles this:

ROM:1922                 ld      hl, SpeedTest_A001 ; bit 0: 1=drop every 31st tick (speedtest result was 1 of 2), 0=drop every 255th tick (speedtest result was 3)
ROM:1922                                         ; bit 1: (during speed test) 1=IRQ has been triggered, 0=IRQ has not been triggered yet
ROM:1922                                         ; bit 2: 1=normal operation, 0=speedtest active
ROM:1922                                         ; bit 3: 1=speedtest got a '1' (faster), 0=speedtest got a '2' or '3' (just right or slower respectively), see 11F9
ROM:1925                 bit     0, (hl)
ROM:1927                 jr      z, DropEvery255thTick ; drop every 255th tick
ROM:1929                 ld      b, 31           ; drop every 31st tick
ROM:192B                 jr      UpdateGlobalTickCounter
ROM:192D ; ---------------------------------------------------------------------------
ROM:192D
ROM:192D DropEvery255thTick:                     ; CODE XREF: IRQ+107 j
ROM:192D                 ld      b, 255          ; drop every 255th tick
ROM:192F
ROM:192F UpdateGlobalTickCounter:                ; CODE XREF: IRQ+10B j
ROM:192F                 ld      hl, GlobalTickCounter_A000 ; keeps a count of how many ticks we've processed so that we can drop ticks from time to time
ROM:1932                 ld      a, (hl)
ROM:1933                 inc     a               ; increment tick counter
ROM:1934                 ld      (hl), a
ROM:1935                 cp      b               ; do we need to drop this tick?
ROM:1936                 jr      nz, UpdateAllTickCounters
ROM:1938                 xor     a
ROM:1939                 ld      (hl), a
ROM:193A                 jp      PostCountersUpdate ; we go here after the tick counters have been updated
ROM:193D ; ---------------------------------------------------------------------------
ROM:193D
ROM:193D UpdateAllTickCounters:                  ; CODE XREF: IRQ+116 j


Additionally, the IRQ service routine will periodically skip certain work if the speed test was a '1' in order to 'slow down' the timer in software.

Therefore, to reliably understand how many seconds a certain number of 'ticks' mean in the Dragon's Lair move data, one can apply this algorithm:

Assume that the hardware clock 'ticks' every 32.768 milliseconds and that the CPU is 4 MHz.  This will yield a speed test result of 2 (normal).

extraTicks = ticks / 30; /* (no remainder, for example, 31/30 would be 1, not 1.0333) */
adjustedTicks = ticks + extraTicks;
seconds = (adjustedTicks) * 0.032768;

3 comments:

  1. Hi Matt,
    Great stuff!

    I would like to add that '3' is a Rev-A board,
    a '2' is a normal Rev-C board,
    and a '1' is a Rev-C board that has had it's "Real Time Clock" (tick maker) multiplied by 8.

    If the board is a '1', the ISR will process the coin drop every time, but process all of timers only one out of 8 times.
    Here is the code to skip most of the ISR 7 out of 8 times...

    ------------------------------------------------------------------------------
    18e1: ld a,(0a001h)
    18e4: bit 3,a // test for modified Rev-C
    18e6: jr z,18f4h

    // fast board - do ISR only one of 8 times
    18e8: ld a,(0a05ch)
    18eb: inc a
    18ec: ld (0a05ch),a
    18ef: and 07h
    18f1: jp nz,19f1h // exit

    --------------------------------------------------------------------------------

    We just don't know why they needed to check for a coin drop so frequently.
    -Shaun

    ReplyDelete
    Replies
    1. Excellent! I didn't get into the ISR enough to see exactly what the 'fastest' behavior was. I did notice the "and 07h" and thought "What on earth are they doing there??" I'm glad you shed some light on it. So when TMV said that the clock was 8x as fast, he was being literal. I didn't realize that.

      Delete
    2. You ever see how fast a kid can cram tokens into a coin mech Shaun? LOL

      Delete