Kajar notes of ChronoRET, rebuilding Chrono Trigger one matched routine at a time

10 August 2026: the animation-frame gate

One routine today, Sub_CB3A (162 bytes), the gate at the start of every sprite-slot init routine. Scoping around it corrected yesterday's picture of the call chain and mapped a new cluster at $CB3A–$D800.

Correcting yesterday's call chain

The goal was to scope the routines at $CF2D, $D299, $D555 and $D617 that call the slot allocators, and the $47xx code with its JSR Sub_E12A at $C0:47E9. Scoping showed the call chain from session 27 was backwards. Those four routines are not callers of a $47xx midlayer. They are sprite-slot init bodies that call Sub_E97A/Sub_E9AA directly, and nothing in $CC00–$D800 calls the $47xx code at all. The two are independent.

The bytes were always fine; every make diff passed. The mistake was in the prose: I had sketched a call graph from cross-reference data without checking the bytes at each address. The assembler enforces bytes, not descriptions, so the lesson is to check the disassembly before writing about who calls what.

The sprite-slot init cluster

$C0:CB3A–$D800 (about 2,000+ bytes, 8+ routines) is a family of sprite-slot init routines with a shared template:

  1. Gate. LDA $1780,X; CMP #$03; BNE: for entity type $03, skip the gate and use $1301,X directly. Otherwise JSR Sub_CB3A; if it returns CLC, exit.
  2. Same-frame check. Compare the frame byte from Sub_CB3A with $0F01,X (the frame type loaded now). If equal, the sprite is already set up: CLC+RTS with no new allocation.
  3. Slot allocation. JSR Sub_E952, Sub_E97A or Sub_E9AA (1, 2 or 3 slots) by sprite type.
  4. DMA tile loop. Read the frame-data table via [$D3],Y; for each 16-bit entry, BIT #$4000: set → JSR Sub_E534 ($FD00-table fill), clear → JSR Sub_E687 (bank-switched tile copy). 16 or 32 passes.
  5. OAM staging. Write frame attributes (tile base, size bits, position offsets) to the $7F:48XX,X staging buffer.
  6. Finish. INC $1B00,X (mark the slot active); RTS.

All the callees (Sub_CB3A, Sub_E952, Sub_E97A, Sub_E9AA, Sub_E534, Sub_E687) are now matched.

Six-step flow: gate, same-frame check, claim slots, copy tiles, stage OAM, mark the slot, with early exits

The template every sprite-slot init routine follows. The first three steps can each end the call with carry clear; the rest always run once a slot is claimed.

Sub_CB3A: the animation-frame gate

Sub_CB3A at $CB3A–$CBDB (162 bytes) is the shared first step. It computes dp $D6, a pointer into a frame-data table in bank $E4, from three entity fields:

$1600,X value Formula for $D6
0 $1500,X (base offset only)
2 2 × $1480,X + $1500,X
> 0 (not 2) 3 × $1480,X + $1500,X
< 0 (bit 7 set) $1480,X + $1500,X

It then adds animType × 4 + animRowHi to reach the current frame row, and reads the frame byte at [$D6]:

  • not $FF: SEC + RTS, the caller goes ahead with init
  • $FF, type 2: decrement the timer at $7F0B01,X. Still counting: CLC + RTS (skip this frame). Zero: also CLC + RTS (the caller handles the transition)
  • $FF, other type: wind $D6 back by animRowHi to the row start, store $FF to $1681,X (row finished), re-read the wrapped frame byte, SEC + RTS

The type 2 branch uses $1781,X instead of $1680,X as the column multiplier: a two-speed animation rate driven by the $7F0B01,X countdown.

Matching notes. There are no BRLs; every branch is in 8-bit range.

  • AND #$00FF / CMP #$0002 in M=0 need explicit 16-bit immediates.
  • LDA [$D6] assembles as opcode $A7 (dp indirect long).
  • LDA $7F0B01,X / STA $7F0B01,X are long indexed ($BF/$9F); asar uses long mode when the bank byte is explicit.
  • STA $D6, LDA $D6, SBC $D9, ADC $D9 and ADC $D6 use raw hex direct-page addresses, which avoids the equate-name quirk that forces long mode.

make diff: 4,194,304 of 4,194,304 bytes identical on the first try.

The rest of the cluster (identified, not matched)

Routine Range Bytes Slot count Tile loop
Sub_CBDC $CBDC–$CDC7 492 1 (Sub_E952) 16 tiles
Sub_CDC8 $CDC8–$CEF4 301 1 (Sub_E952) 16 tiles (no CB3A gate)
Dispatch block $CEF5–$D289 ~660 2 (Sub_E97A) 32 tiles, BRL dispatch
Sub_D28A $D28A–$D30C 131 2 (Sub_E97A) 16 tiles
Sub_D30D $D30D–$D545 ~569 2 (Sub_E97A) varies
Triple-slot A $D546–$D607 ~194 3 (Sub_E9AA) 8 tiles
Triple-slot B $D608–$D800 ~504 3 (Sub_E9AA) 16 tiles

The $47xx code is still uncharted. Its entry points are $46DF, $475C and $47A5. It loads entity data from bank $E4, checks $1201,X bits 0–1 and calls Sub_E12A. Its several entry points need careful boundary work, so it waits.

The day in numbers

Session Routine Address Bytes
28 Sub_CB3A $C0:CB3A–$CBDB 162
Total 162

Running total: ~14,083 bytes across 75 functions.

Next

Sub_D28A (131 bytes, every callee matched, a clean fit to the template), or Sub_CBDC (492 bytes, same callees).

← Back to devlog