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:
- Gate.
LDA $1780,X; CMP #$03; BNE: for entity type$03, skip the gate and use$1301,Xdirectly. OtherwiseJSR Sub_CB3A; if it returnsCLC, exit. - Same-frame check. Compare the frame byte from
Sub_CB3Awith$0F01,X(the frame type loaded now). If equal, the sprite is already set up:CLC+RTSwith no new allocation. - Slot allocation.
JSR Sub_E952,Sub_E97AorSub_E9AA(1, 2 or 3 slots) by sprite type. - 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. - OAM staging. Write frame attributes (tile base, size bits, position offsets) to the
$7F:48XX,Xstaging buffer. - 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.
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: alsoCLC+RTS(the caller handles the transition)$FF, other type: wind$D6back byanimRowHito the row start, store$FFto$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 #$0002in M=0 need explicit 16-bit immediates.LDA [$D6]assembles as opcode$A7(dp indirect long).LDA $7F0B01,X/STA $7F0B01,Xare long indexed ($BF/$9F); asar uses long mode when the bank byte is explicit.STA $D6,LDA $D6,SBC $D9,ADC $D9andADC $D6use 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).