9 August 2026: the Sub_E12A cluster closes, and the slot allocators
All six callees of Sub_E12A are matched in one pass, which completes the sprite init arc. Then the three sprite-slot allocators that sit just before it: 1,169 new bytes in 9 functions (sessions 26 and 27).
What the cluster does
Sub_E12A runs the 96-entry WRAM fill behind the sprite-init DMA. On each pass it checks bit 14 of the scene word:
- set →
JSR Sub_E534: reads 32 bytes through a lookup table at$FD:0000and streams them to WRAM via WMDATA - clear →
JSR Sub_E687: works out which bank holds the tile data and copies 64 bytes (32 tiles × 2 bytes) straight to the WRAM address
Sub_E687 then tail-calls one of four bank-copy stubs, chosen by a bank ID in dp $CF.
Sub_E534: the $FD00 table path
Sub_E534 ($E534–$E686, 339 bytes; the earlier estimate was 341) extracts a byte index from the scene word, reads 32 bytes through the $FD:0000 table, and writes each to $2180 (WMDATA). It is fully unrolled: 32 blocks of LDA [$CD],Y / TAX / LDA $FD00,X / STA $2180 / INY, no loop, like the sprite renderer. It enters in 16-bit A (from Sub_E12A's exit state), switches to 8-bit for the 32 writes, and restores 16-bit before returning.
Sub_E687: the bank dispatch
Sub_E687 ($E687–$E738, 178 bytes) computes an offset into the tile-data bank and dispatches on dp $CF:
$CF value |
Target | Bank |
|---|---|---|
$7F |
Sub_E8C2 |
$7F (local via DB=$7F) |
$D2 |
Sub_E7BC |
$D2 |
$D3 |
Sub_E83F |
$D3 |
$D4 |
Sub_E739 |
$D4 |
$D5 |
inline .d5copy |
$D5 |
It uses CMP + BNE + BRL chains, because the stubs are more than 128 bytes away. Each BRL is a tail call: the stub's RTS returns to Sub_E687's caller. With no JSR/RTS pair, the stubs had to be in scope in the same pass.
Sub_E8C2, the $7F stub, differs from the others in one way: it uses LDA.w $0000,X (opcode BD, absolute indexed) instead of LDA $7F0000,X (opcode BF, long indexed). It has already set DB = $7F with PLB, so the absolute load reads $7F:0000+X and the long form would waste a byte. LDA.w stops asar from upgrading it to long.
The db $A9,$7F trick
Each stub starts PHB / LDA #$7F / PHA / PLB. In the ROM, LDA #$7F at $E739 is the 2-byte 8-bit form, A9 7F. But in the source the stubs follow Sub_E687, which contains a SEP #$20 early (for the CMP against $CF) and a REP #$20 in the inline .d5copy path. By the stubs, asar tracks M=0, and LDA #$7F would assemble as A9 7F 00, three bytes. The fix is db $A9,$7F, which emits the two bytes regardless of mode.
The quirk can cost an hour the first time. In 16-bit mode asar zero-extends the immediate, but the ROM sometimes has the two-byte form, with the next byte being the next instruction. It wasn't in asar Quirks, so the first commit of session 27 added it there, next to the BRL offset quirk and the STA-with-equate quirk:
One set of bytes, two meanings
Sub_E534 and Sub_E687 share a header sequence. The ROM bytes at $E534 are:
With M=1 (Sub_E534's entry state): AND #$FF / ORA [$0A] / ASL / ASL / ASL / ASL / STY $C5, 10 bytes.
With M=0 (Sub_E687's entry state): AND #$07FF / ASL / ASL / ASL / ASL / ASL / STY $C5, also 10 bytes.
The same bytes are correct for both call paths: Sub_E534 multiplies by 16, Sub_E687 by 32 (the extra ASL in M=0 supplies the factor of 2 that the M=1 path gets from the ORA). It looks like a deliberate encoding choice. So Sub_E534 is written in M=1 encoding (its real entry state from Sub_E12A) and Sub_E687 in M=0, and both emit 29 FF 07 0A 0A 0A 0A 0A 84 C5. The diff proved it.
The shared opening of Sub_E534 and Sub_E687: ten identical bytes that decode into two different, equally valid instruction streams.
Result
First assembly, no corrections: 1,025 bytes in 6 routines across $E534–$E934.
| Block | Range | Bytes | Status |
|---|---|---|---|
Sub_E12A |
$E12A–$E533 |
1034 | Matched (session 25) |
Sub_E534 |
$E534–$E686 |
339 | Matched |
Sub_E687 |
$E687–$E738 |
178 | Matched |
Sub_E739 |
$E739–$E7BB |
131 | Matched |
Sub_E7BC |
$E7BC–$E83E |
131 | Matched |
Sub_E83F |
$E83F–$E8C1 |
131 | Matched |
Sub_E8C2 |
$E8C2–$E934 |
115 | Matched |
The whole arc: 2,059 bytes, 7 routines, sessions 25–26.
Who calls Sub_E12A
A raw byte search for JSR $E12A (20 2A E1) found exactly two call sites in bank $C0:
$C0:0212, inside the already-matchedSub_01A5. The main path: afterSub_595Cdispatches the entity types, it reads dp$AE, and if the slot index is non-negative copies it to X and dp$6Dand callsSub_E12A.$C0:47E9, inside an uncharted routine around$4700. The code checks$1201,X AND #$03 == $03first, so this looks like a per-slot update path that re-runs sprite init for one flag combination. Noted, not matched.
Two callers fits a scene-load function, not a per-frame one.
The slot-allocation trio
Between the matched Sub_E935 and Sub_E9E2, the range $E952–$E9E1 held three uncatalogued routines: the allocation counterparts to the deallocation routines Sub_E9E2/E9FF/EA1F.
The $0BC0 table holds up to eight one-byte slots. Sub_E935 sets all eight to $80, the "free" marker (bit 7 set; an owned entry has bit 7 clear, and the allocators' BPL skips past it). Each allocator scans the table and claims the first free position:
| Routine | Size | Searches | Claims |
|---|---|---|---|
| Sub_E952 | 40 B | $0BC0[0..3] single | $0BC0[X] |
| Sub_E97A | 48 B | $0BC0/$0BC1[0..2] pair | $0BC0[X],$0BC1[X] |
| Sub_E9AA | 56 B | $0BC0/$0BC1/$0BC2[0..1] triple | $0BC0[X],$0BC1[X],$0BC2[X] |
All three share a post-claim body: turn the table index X into a WRAM staging base ($3800 + X × $0200) and store it at $0D80+slot. Carry signals the result: SEC for success, CLC for failure.
Sub_E97A stops at CPX #$03 (positions 0–2), not #$04. Pair positions overlap: position 0 claims ($0BC0, $0BC1) and position 1 claims ($0BC1, $0BC2). Each search stops where a run would pass entry 3, so all three variants share entries 0–3 (a triple starts at 0 or 1) and none of them claims entries 4–7. All three match byte-exact.
Where each allocator may claim: one entry at 0-3, two adjacent entries starting at 0-2, three starting at 0-1. All three stay inside entries 0-3 of the eight; entry n is the $200-byte chunk at $7F:3800 + n × $200.
Their callers ($CC0D, $CF2D, $D299, $D555, $D617) are all in the uncharted $CC00–$D600 range. My reading at the end of the day was that this range is a per-entity-type init layer above Sub_E12A: it calls the allocators, sets up the hardware multiply for WRAM offsets, then calls Sub_E12A or its kin. (The next day showed the call chain was not quite like that.)
The day in numbers
| Session | Routine | Address | Bytes | Running total quoted |
|---|---|---|---|---|
| 26 | Sub_E534 |
$C0:E534–$E686 |
339 | |
| 26 | Sub_E687 |
$C0:E687–$E738 |
178 | |
| 26 | Sub_E739 |
$C0:E739–$E7BB |
131 | |
| 26 | Sub_E7BC |
$C0:E7BC–$E83E |
131 | |
| 26 | Sub_E83F |
$C0:E83F–$E8C1 |
131 | |
| 26 | Sub_E8C2 |
$C0:E8C2–$E934 |
115 | 13,777 (71 functions) |
| 27 | Sub_E952 |
$C0:E952 |
40 | |
| 27 | Sub_E97A |
$C0:E97A |
48 | |
| 27 | Sub_E9AA |
$C0:E9AA |
56 | 13,921 (74) |
| Total | 1,169 |
Bank $C0 ended the day at 13,450 bytes.
Next
A systematic pass through the $CC00–$D600 range, which uses these allocators.