# Ice Climber (U) — Pass 3 Complete (2026-10-21) ROM: `NROM/Ice Climber (U)/Ice Climber (U).nes` (24,592 bytes, iNES). Status: **Pass 1 complete - 74.6% code; Pass 2 complete; Pass 3 complete.** `prg.s` is a real disassembly: 6,112 instructions (12,230 bytes as code), **973 label definitions all named** - 76 semantic routine names + 212 `SubC` entry points + 685 `_Block[_n]` internal branch targets. `locals.inc` holds **157 zero-page slots with 9 semantic aliases** added. It builds **byte-identical**: `make` assembles `header.s`, `prg.s` and `chr.s`, links them with `ld65` and compares the result against `rom.nes`. **Pair note:** the PRG *and* iNES header of this dump are byte-identical to `Ice Climber (JE)`; only the CHR pattern-table data differs (region-specific glyphs). The two `prg.s` files are now identical after Pass 3 completion (copied from JE 2026-10-19). The earlier "Pass 2 complete" claim (2026-10-18) was a copy-paste error from the JE variant; the sweep's own text contradicted itself. No Pass 2 map file existed for `(U)`. Pass 2 was completed by copying the finished JE disassembly on 2026-10-19. **Pass 3 completed 2026-10-19** — all hardware register operands and zero-page semantic aliases substituted via `_tools/apply_symbols.py`; multi-alias residues resolved via `sites.tsv`; data tables/structures named. --- ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. **RESET** is at `$C014` (`Reset`). The CPU comes here out of reset with the PPU and APU in their power-on state and the zero page and RAM uninitialised. The reset path calls: | Address | Routine | |---------|---------| | `$C01E` | `WaitVBlank_1` (falls through) | | `$C024` | `WaitVBlank2_1` (falls through) | | `$C041` | `ClearZeropage` (falls through) | | `$C05E` | `InitAPU_3` | | `$C141` | `UpdateGameState2_5` | | `$C058` | `InitAPU_1` (falls through) | | `$CACE` | `InitSprites_4` | | `$C058` | `InitAPU_1` | **NMI** is at `$C076` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| **IRQ** is at `$FFF0` (`$FFF0`) - a different address from RESET. ## ROM Facts | Property | Value | |----------|-------| | Mapper | 0 (NROM) — from the iNES header | | PRG | 16 KB (header claims map at CPU $C000–$FFFF) | | CHR | 8 KB | | Mirroring | Horizontal | | Battery-backed flag | clear | | ROM SHA-256 | `de0ff27935752e0b5a7840d1f4ae9fe04f89236be0af79f5554fbf4f1f17d427` | | PRG SHA-256 | `6913935b8d55ade070555416d330472839f4cefbf865c15c8248796d2bc5cf29` | --- ## Interrupt Vectors (taken from PRG $3FFA–$3FFF) | Vector | Target | Note | |--------|--------|------| | NMI | `$C076` | per-frame heartbeat | | RESET | `$C014` | enters at $C014 inside the first pages (the Donkey Kong Jr.-style non-$C000 entry) | | IRQ | `$FFF0` | lands in the vector-page padding — unused placeholder | --- ## Contents | File | Purpose | |------|---------| | `Ice Climber (U).nes` | original ROM image (reference) | | `rom.nes` | the same image under a space-free name; the build compares against it | | `prg.bin` / `header.bin` / `chr.bin` | split image, produced by `_tools/splitnes.py` | | `header.s` / `chr.s` | one-line `.incbin` of the two splits, so the whole ROM is rebuilt | | `prg.s` | Pass 1/2/3: classified code/data split, 6,112 instructions, **973 labels all named** (76 semantic + 212 `SubC` + 685 `_Block`), canonical audit comments; **Pass 3 symbols applied**; **104 data-table labels** (see the inventory below) | | `symbols.inc` | shared NES hardware constants (PPU/APU/controller) | | `locals.inc` | 157 zero-page slots, **9 semantic aliases** (e.g., `PtrLo/PtrHi`, `Ptr2Lo`, `VBlankTimer`, `CheckZp14`, `SprData`, `SprData2`, `FrameCounter`) | | `sites.tsv` | per-line multi-alias resolutions for `_tools/apply_symbols.py` (3 entries) | | `iceclimber_label_map.tsv` / `iceclimber_full_map.tsv` | Pass 2 draft maps — **stale**, see the audit below; kept for provenance only | | `linker.cfg` | HEADER at $0000, PRG at $C000 (16 KB), CHR at $0000 (8 KB), `fill = yes` | | `Makefile` | links `reconstructed.nes` and `cmp`s it against `rom.nes` | | `reconstructed.nes` | build output from the last verified build | The header and the CHR **are** linked, so `make` proves the whole 24,592-byte image, not just the PRG. ## Build ```bash cd "NROM/Ice Climber (U)" make # → cmp reconstructed.nes rom.nes → "Byte-identical ROM verified." ``` --- ## Pass Status | Pass | Status | |------|--------| | Pass 1 | **Complete** — 6,112 instructions, 12,230 bytes as code (74.6%), 4,154 bytes as data, byte accounting exact (16,384) | | Pass 2 | **Complete** — 973 label definitions all named (76 semantic + 212 `SubC` + 685 `_Block`); 157 zero-page slots with 9 semantic aliases in `locals.inc` | | Pass 3 | **Complete** — 91 operands substituted via `_tools/apply_symbols.py` (23 single-name + 68 hardware); 3 multi-alias residues resolved via `sites.tsv`; **all 99 data blocks interpreted and inventoried, 104 data-table labels** (2026-10-21) | | Promotion | **Done** — the project lives in `NROM/Ice Climber (U)/`, links `header.s`/`prg.s`/`chr.s` and `make` compares the whole ROM against `rom.nes` | --- ## Pass 2 Completion (2026-10-19) Completed by copying the finished `Ice Climber (JE)` disassembly (PRG is byte-identical). The JE Pass 2 work was done fresh from the committed split on 2026-10-19: - **76 semantic routine names** preserved: `Reset`, `WaitVBlank_1`, `WaitVBlank2_1`, `ClearZeropage`, `InitAPU_1`, `InitAPU_3`, `MainGameLoop`, `GameLoopEntry_1`, `NmiHandler`, `NmiFrameUpdate`, `NmiFrameUpdate2`, `GameStateUpdate`, `UpdateGameState3_1`, `UpdateGameState4`, `UpdateGameState5`, `UpdateGameState6`, `GameLoopEntry_4`, `UpdateGameState2_4`, `UpdateGameState2_5`, `WaitVBlankFrame2`, `InitSprites_1`, `CopySprites`, `UpdateSprites_1`, `UpdateSprites2_1`, `UpdateSprites2_3`, `CheckZp34`, `UpdateSprites2_4`, `UpdateSprites3_1`, `InitSprites_3`, `UpdateSprites_3`, `UpdateSprites2_5`, `UpdateSprites3_3`, `UpdateSprites4_1`, `UpdateSprites4_3`, `UpdateSprites4_4`, `GameLoopEntry_5`, `InitGameState`, `UpdateSprites2_8`, `UpdateSprites2_9`, `InitSprites2_1`, `ClearSpriteRAM`, `UpdateSprites2_10`, `UpdateSprites2_11`, `UpdateSprites3_6`, `UpdateSprites4_5`, `UpdateSprites5_1`, `UpdateSprites6`, `UpdateSprites7_1`, `UpdateSprites7_3`, `UpdateSprites8`, `UpdateSprites9`, `UpdateSprites10`, `UpdateSprites11`, `UpdateSprites12`, `UpdateSprites13`, `InitSprites_4`, `InitSprites3`, `UpdateController`, `UpdateController2_4`, `UpdateSprites_4`, `UpdateSprites2_12`, `UpdateSprites4_7`, `UpdateSprites4_6`, `UpdateSprites5_4`, `UpdateSound4_11`, `UpdateSound6`, `UpdateSound12_4`, `UpdateSound24_1`, `UpdateSound26_1`, `UpdateSound26_11`, `UpdateSound26_13`, `UpdateMusicChannel19`. - **212 `SubC` entry points** for all remaining `jsr`/`jmp` targets that lacked semantic names (e.g., `SubCC166` at $C166, `SubCC1D7` at $C1D7, etc.). - **685 `_Block[_n]` internal branch targets** derived from control flow within each routine (e.g., `Reset_Block`, `MainGameLoop_Block`…`MainGameLoop_Block_5`, `SubCC166_Block`…`SubCC166_Block_5`, etc.). ### Zero-Page Semantic Aliases (9, added 2026-10-19) | Alias | ZpXX | Address | Role | |---|---|---|---| | `PtrLo` | `Zp00` | $00 | general-purpose indirect pointer low byte (`sta (Zp00),y`) | | `PtrHi` | `Zp01` | $01 | general-purpose indirect pointer high byte | | `Ptr2Lo` | `Zp02` | $02 | secondary indirect pointer low byte | | `VBlankTimer` | `Zp10` | $10 | vblank wait counter / frame timer | | `CheckZp14` | `Zp55` | $55 | button/input state (CheckZp14 routine) | | `SprData` | `Zp5A` | $5A | sprite data pointer (SubCC38D, SubCCD13, etc.) | | `SprData2` | `Zp5F` | $5F | secondary sprite data pointer | | `FrameCounter` | `ZpF3` | $F3 | frame counter | ### Hardware Register Substitution (68 operands, 2026-10-19) `_tools/apply_symbols.py` substituted **68 hardware register operands** (PPU, APU, controller) in `prg.s`. Build remains **BYTE-IDENTICAL**. ### Pass 3 Symbol Application (2026-10-19) `_tools/apply_symbols.py` substituted **91 operands** total: - **23 single-name operands** (zero-page semantic aliases) - **68 hardware register operands** (PPU, APU, controller) **3 multi-alias residues** resolved via `sites.tsv` (copied from JE): | Line | Operand | Chosen Alias | |------|---------|--------------| | 706 | `a:$005F,y` | `SprData2` | | 1612 | `($0002)` | `Ptr2Lo` | | 11140 | `a:$0000,y` | `PtrLo` | ### Data Tables / Structures Named (Pass 3, 2026-10-19, revised 2026-10-21) The 25 generic names the first Pass 3 gave the project (`InitialSpriteData`, `SpriteDataTable1` ... `VectorTableAndPadding`) have been replaced by **104 labels derived from the structures the code actually uses**, and every one of the project's 99 data blocks is now described in the [Pass 3 data-table inventory](#pass-3-data-table-inventory) below. Several of the old names were simply wrong: `SpriteAnimTable1`, `SpriteAnimTable2`, `SpriteDataTable3`, `EnemyAnimData1`, `EnemyAnimData2`, `SoundEffectTable1`, `MusicChannelData3` and `MusicChannelData4` sat on blocks that are **code the tracer could not reach**, not tables, so those labels were removed rather than renamed. The 2026-10-21 pass also used a Mesen Code Data Logger capture of a played session to tell the genuine tables from the unreached code, and to find the blocks that are read at run time through an address the static tracer cannot follow. ## Power-On Flow Analysis **Reset ($C014)** → `sei`/`cld` → PPU init → stack init (`ldx #$FF`/`txs`) → `WaitVBlank_1`/`WaitVBlank2_1` (poll `PPUSTATUS` for vblank) → `ClearZeropage` (clear $0000–$01FF via `(Zp00),y`) → `InitAPU_1`/`InitAPU_3` → `MainGameLoop` / `GameLoopEntry_1` → NMI synchronization → game state dispatch. **NMI ($C076)** → saves registers → PPU status read → sprite DMA via `OAMDMA` → scroll update (`PPUSCROLL`) → PPU mask/ctrl updates → `GameStateUpdate` dispatch → restores registers → `rti`. **IRQ ($FFF0)** → `rti` immediately (unused placeholder in vector-page padding). **Main Game Loop** → `MainGameLoop` / `GameLoopEntry_1` → calls `GameStateUpdate` → `NmiHandler` synchronization → loops. --- ## Pass 3 data-table inventory Every `; ---- data $XXXX-$YYYY ----` region in `prg.s` is listed here - all 99 of them. A region can hold a table, a table *and* unreached code, or code alone: the tracer's data blocks are contiguous runs of bytes it never reached, so the block boundaries are not the table boundaries. ### How the blocks were classified Three sources of evidence were used, and they agree: 1. **The consuming instruction.** Every absolute operand in `prg.s` was collected and matched against the block it lands in, so each row can name the instruction that reads the bytes and what it does with them. 2. **Mesen's Code Data Logger.** The ROM was loaded in Mesen, a session was played (title screen, first stage, bonus stage, ~8,300 PRG bytes executed) and the CDL was saved. The CDL marks every PRG byte as *code* (fetched as an opcode), *data* (read as data) or *untouched*, which separates the genuine tables from the unreached code and shows which bytes are read at run time through an address the static tracer cannot follow. 3. **The bytes themselves.** Record size, encoding and units were read off the data and checked against the arithmetic the consumer performs on them (bit masks, `AND #$1F`, `lsr a` four times, `cmp` against a pair of bounds, and so on). 4. **The CHR ROM as an oracle.** The two dumps differ only in their CHR, so a byte that is a CHR tile index can be recognised by rendering the tile: `$11` is the present/box, `$1A` and `$2A` are the icicle and the ice block, `$16`/`$17` are the Yeti. That is what identifies the values of the table at `$C610` and the two tile-row runs at `$EABF` and `$EADD` as graphics data rather than as opaque numbers. Where the layout could not be established the row says so and records the evidence; nothing is named after what it might be. ### Inventory | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000-$C013` | 20 | `VramScriptPointerLowBytes` (`$C000`), `VramScriptPointerHighBytes` (`$C00A`) | two parallel 10-byte arrays of pointer bytes: entry *i* is the 16-bit address ($C000+i) \| (($C00A+i) << 8). The routine at `$C14E` loads the low byte with `lda $C000,x` and the high byte with `lda $C00A,x` into the zero-page pointer pair `$00`/`$01` and calls the VRAM streamer at `$CB78`. Only index 0 is used in this build, and it points at RAM `$0301` - the sprite buffer the game builds at run time and streams to the nametable | `lda $C000,x` at `$C150`, `lda $C00A,x` at `$C155` | | `$C2ED-$C38C` | 160 | - | unreached **code**: the 160 bytes decode as 6502 instructions throughout (`LDX $57; BNE +$33; JSR $C81D; ...`). It is the second target of the dispatch table at `$C440`; the CDL capture of a played session never executed it | not referenced by any instruction | | `$C3A4-$C435` | 146 | - | unreached **code**: the bytes decode as instructions throughout, and the CDL capture marks **all 146 bytes as executed code** - it is the fifth target of the dispatch table at `$C440`, reached through an index the static tracer cannot follow | not referenced by any instruction | | `$C440-$C449` | 10 | `GameStateHandlerPointerTable` | 5 x 16-bit little-endian routine addresses: `$C1D7`, `$C2ED`, `$C2AD`, `$C2B0`, `$C3A4`. Two of the five targets are the unreached code blocks above, which is how the tracer recovered them as routine entries | not referenced by any instruction (reached through a computed index) | | `$C452-$C4BF` | 110 | `OamShadowSpriteRecord` (`$C45D`) | `$C452-$C45C` = 11 unreferenced bytes whose layout is not established; `$C45D-$C45F` = a 3-byte OAM-shadow record (tile `$6F`, X `$00`, Y `$38`) that the routine at `$C4C0` copies into the sprite shadow at `$0201`; `$C460-$C4BF` = unreached code (the CDL marks 82 of the 110 bytes as executed) | `lda $C45D,x` at `$C4CD` | | `$C512-$C541` | 48 | `PaletteRgbColorTable` | 16 records of 3 bytes. Each record's three bytes are loaded into the zero-page pointer bytes as a 24-bit little-endian value by the routine at `$C542` and are then shifted right one bit at a time by the bit-reader at `$C5D3`, which takes the top four bits as a 4-bit value. The values are RGB triples (`$000000`, `$FFFFFF`, `$CFE7F3`, `$333333`, `$0F0F0F`, `$F0F0F0`, `$CCCCCC`, `$FF00FF`, `$FFFFFC`, `$3F00FC` ...), i.e. the game's sixteen palette colours in four sub-palettes of four. The index is the object's own value multiplied by three | `lda $C512,y` at `$C55A`, `lda $C513,y` at `$C55F`, `lda $C514,y` at `$C564` | | `$C5A1-$C5A6` | 6 | - | six bytes (`$E7 $3C $FC $3F $CF $E3`) that the CDL marks as read as data at run time, but through an address the tracer cannot follow; no instruction in this dump names an address inside the block and the layout is not established | not referenced by any instruction | | `$C5EC-$C5EC` | 1 | - | a single `$2C` byte. It is not padding: the CPU executes it as the opcode of a three-byte `BIT abs`, whose two operand bytes are the first two bytes of the routine the tracer reached at `$C5ED`. The instruction after it starts at the same address in both readings, so only the opcode is misplaced | not referenced by any instruction | | `$C5F8-$C5F8` | 1 | - | single `$2C` byte: the opcode of the `BIT abs` whose operand bytes are the first two bytes of the routine at `$C5F9` | not referenced by any instruction | | `$C5FB-$C5FB` | 1 | - | single `$2C` byte: the opcode of the `BIT abs` whose operand bytes are the first two bytes of the routine at `$C5FC` | not referenced by any instruction | | `$C607-$C607` | 1 | - | single `$2C` byte: the opcode of the `BIT abs` whose operand bytes are the first two bytes of the routine at `$C608` | not referenced by any instruction | | `$C610-$C61F` | 16 | `PseudoRandomByteTable` | 16 x 8-bit values in `$11`-`$38` (`$11 $22 $11 $38 $29 $28 $19 $21 $1A $28 $2A $12 $2A $22 $14 $12`). The routine at `$C620` reads the entry at the counter `$03FA` masked to four bits and the entry *before* it, and takes one nibble from each: the high nibble of the first into `$03FB`, the low nibble of the second into the zero-page flags `$14`/`$15`. The table is the game's source of pseudo-random bits | `lda $C610,x` at `$C650` (and `ldy $C60F,x` at `$C653`, which reads the same table one entry earlier) | | `$C680-$C6FA` | 123 | `SpriteParameterRecordTable` (`$C680`), `NametableCopyAddressLowBytes` (`$C6F7`), `NametableCopyAddressHighBytes` (`$C6F9`) | `$C680-$C6A3` = 9 records of 4 bytes of which 3 are used: the sprite builder at `$C8E0` indexes it with the high nibble of the byte in `$0300` (0-8) and a stride of 4, copies the first two bytes into the sprite buffer at `$0301` and uses the third, masked to three bits, as a count. `$C6A4-$C6F6` is a further run of the same 3-bytes-per-4-bytes shape; the CDL marks `$C698-$C6F6` as read at run time, but no instruction names an address inside it `$C6F7-$C6FA` = two parallel 2-byte arrays holding the two nametable addresses `$10A0` and `$1204` from which the routine at `$C6FB` copies 512 bytes of nametable into RAM `$0400` | `lda $C680,y` at `$C8FC`, `$C906`, `$C910`; `lda $C6F7,x` at `$C704`; `lda $C6F9,x` at `$C6FE` | | `$C839-$C83C` | 4 | `SpriteMoveDeltaTable` | 4 x 8-bit signed deltas (`$22 $17 $22 $27`) added to an object's 16-bit position (`ZpE8`/`ZpE6`); the index is the object's state byte `Zp2F,x` | `adc $C839,y` at `$C85D` | | `$C897-$C899` | 3 | - | unreached **code**: three bytes that decode as `JSR $C15D`, a complete instruction | not referenced by any instruction | | `$CBB3-$CC04` | 82 | `ObjectPointerLowByteTableA` (`$CBB3`), `OamGroupBaseOffsetTable` (`$CBBF`), `ObjectPointerLowByteTableB` (`$CBC1`), `ObjectRangeBoundTable` (`$CBDC`), `ObjectLimitValueTable` (`$CBE8`), `ObjectFlagMaskTable` (`$CBED`), `OamGroupSpriteDeltaTable` (`$CBF5`) | a bundle of small tables: `$CBB3` = pointer low bytes (index = the object's state byte); `$CBBF` = 4 x 8-bit OAM-shadow base offsets, one per four-sprite group (`$24 $D6 $17 $6E`); `$CBC1` = a second run of pointer low bytes; `$CBDC` = 6 pairs of 8-bit signed comparison bounds scanned two bytes at a time by the range test at `$CEDB`; `$CBE8` = 5 x 8-bit limit values; `$CBED` = 8 x 8-bit flag masks OR-ed into the object's flag byte; `$CBF5` = 4 x 8-bit OAM-slot deltas (`$00 $04 $08 $04`) added to a group's base offset. `$CBFB-$CC04` = unreached code | `ldy $CBBF,x` at `$CE0C` and `$CE31`, `adc $CBF5,x` at `$CE13`, `cmp $CBDC,y` at `$CEDD`, `cmp $CBDD,y` at `$CEE2`, `lda $CBDC,y` at `$CF2B`, `lda $CBDA,y` at `$D173`, `lda $CBB3,y` at `$D17C`, `lda $CBC1,y` at `$D1B2`, `lda $CBC6,y` at `$D1C4`, `lda $CBC7,y` at `$D1CC`, `cmp $CBE8,y` at `$D1FB`, `lda $CBED,y` at `$CFB1`, `lda $CBBF,x` at `$D742` | | `$CC67-$CC9D` | 55 | - | unreached **code**: the 55 bytes decode as 6502 instructions throughout (`LDA #$04; JMP $D4F5; JSR $F471; ...`) | not referenced by any instruction | | `$CD20-$CD60` | 65 | `ObjectDispatchPointerTable` | `$CD20-$CD31` = 9 x 16-bit little-endian dispatch addresses (`$CD32`, `$CE5D`, `$CD38`, `$D008`, `$D05D`, `$D008`, `$D0AA`, `$D008`, `$D500`), all of them code addresses. `$CD32-$CD60` = unreached code (the CDL marks it as executed) | not referenced by any instruction (reached through a computed index) | | `$CD8D-$CD94` | 8 | `SpriteFrameLimitTable` (`$CD8D`), `SpriteStageLimitTable` (`$CD91`) | two parallel 4-byte tables of 8-bit countdown limits (`$02 $09 $06 $04` and `$04 $02 $02 $04`): the first is compared against a per-sprite frame counter, the second against a per-sprite stage counter | `cmp $CD8D,y` at `$CD99`, `cmp $CD91,y` at `$CDA6` | | `$CE53-$CE53` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CE54` | not referenced by any instruction | | `$CE7D-$CE7D` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CE7E` | not referenced by any instruction | | `$CE8A-$CE8A` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CE8B` | not referenced by any instruction | | `$CF14-$CF14` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CF15` | not referenced by any instruction | | `$CF57-$CF5A` | 4 | `ObjectPointerLowBytes` (`$CF57`), `ObjectPointerHighBytes` (`$CF59`) | two parallel 2-byte arrays of pointer bytes; entry *i* is the 16-bit address ($CF57+i) \| (($CF59+i) << 8), i.e. `$0301` and `$0D80` | `lda $CF57,y` at `$CFA4`, `lda $CF59,y` at `$CF8F` | | `$CFD0-$CFD0` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CFD1` | not referenced by any instruction | | `$CFEA-$CFEA` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$CFEB` | not referenced by any instruction | | `$D008-$D009` | 2 | - | unreached **code**: two bytes that decode as `LDA $6A,X` | not referenced by any instruction | | `$D05D-$D06A` | 14 | - | unreached **code**: the bytes decode as instructions throughout (`INC $60; LDA $60; CMP #$14; BCS ...`) and the CDL marks all 14 bytes as executed code. It is the fourth target of the dispatch table at `$CD20` | not referenced by any instruction | | `$D091-$D0A9` | 25 | `ObjectPointerThirdByteTable` (`$D0A6`) | `$D091-$D0A4` = 20 bytes of small values in `$68`-`$6D` terminated by `$00`, read at run time (CDL) through an address the tracer cannot follow. `$D0A6-$D0A8` = 3 x 8-bit bytes (`$80 $C0 $40`) loaded into the third pointer byte `Zp02` | `lda $D0A6,y` at `$D0E7` | | `$D248-$D248` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$D249` | not referenced by any instruction | | `$D251-$D251` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$D252` | not referenced by any instruction | | `$D281-$D281` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$D282` | not referenced by any instruction | | `$D391-$D392` | 2 | `ObjectSlotIndexTable` | 2 x 8-bit RAM slot indices (`$00`, `$05`) used as an X index into the two counters at `$0361` and `$0363` | `lda $D391,x` at `$D3A0` and `$D421` | | `$D48D-$D492` | 6 | `ObjectXRangeLowBytes` (`$D48D`), `ObjectXRangeHighBytes` (`$D490`) | two parallel 3-byte arrays of 8-bit bounds: the low bytes (`$2C $64 $AC`) and the high bytes (`$4C $94 $CC`) of three X bands, `[44,76)`, `[100,148)` and `[172,204)`, tested against the object's X coordinate | `cmp $D48D,y` at `$D49F`, `cmp $D490,y` at `$D4A4` | | `$D501-$D506` | 6 | `ObjectDataPointerHighBytes` (`$D501`), `ObjectDataPointerLowBytes` (`$D505`) | two parallel arrays of pointer bytes: 4 high bytes (`$01 $02 $04 $04`, index = (value >> 2) & 3) and 2 low bytes (`$0A $05`, index = `ZpD5`), giving the 16-bit addresses `$010A` and `$0205` | `lda $D501,y` at `$D51B`, `lda $D505,y` at `$D522` | | `$D75E-$D7DD` | 128 | `ObjectSpeedTable` (`$D75E`), `IceBlockColumnXTable` (`$D762`), `SpriteTypeKeyTable` (`$D77F`), `SpriteTypeValueTable` (`$D785`), `SpriteTypePointerHighBytes` (`$D78B`), `SpriteTypePointerLowBytes` (`$D794`), `SpriteTypeAltValueTable` (`$D79D`), `PlayfieldLeftEdgeTable` (`$D7A3`), `PlayfieldRightEdgeTable` (`$D7AD`), `ObjectStepValueTable` (`$D7B7`), `ObjectStepAltValueTable` (`$D7BD`), `ObjectSpeedLimitTable` (`$D7C8`), `ObjectSpeedLimitAltTable` (`$D7D3`) | the playfield geometry and the sprite-type tables. `$D762` = the 10 X positions of the ice-block columns (`$02 $00 $18 $30 $48 $60 $78 $90 $A8 $C0`, 24 pixels apart). `$D7A3`/`$D7AD` = the 10 left and right edges of the ten playfield rows (`$20 $20 $20 $28 $28 $28 $30 $30 $30 $30` and `$E0 $E0 $D8 $D8 $D8 $D0 $D0 $D0 $D0`). `$D77F` = 6 key bytes searched linearly by the routine at `$DDBF`; `$D785` and `$D79D` = the two 6-entry value tables the same search returns. `$D78B`/`$D794` = 10 high and 10 low bytes of 16-bit pointers. `$D7B7`, `$D7BD`, `$D7C8`, `$D7D3` = small step and speed values in `$04`-`$10` | `lda $D75E,y` at `$D905`, `cmp $D7A3,y` at `$DA70`, `$DB5B`, `$DBD3`, `cmp $D7AD,y` at `$DA78`, `$DB60`, `$DBD8`, `lda $D7AD,y` at `$DA7D`, `lda $D7A3,y` at `$DA87`, `$DAE6`, `$DAEB`, `cmp $D7BD,y` at `$DB58` and `$DB69`, `cmp $D7D3,y` at `$DB7A`, `adc $D7B7,y` at `$DB8A`, `cmp $D7C8,y` at `$DB9D`, `cmp $D7BC,y` at `$F8E8` and `$F909`, `cmp $D77F,y` at `$DDC0` and `$DEED`, `lda $D785,y` at `$DDC5`, `lda $D79D,y` at `$DEF2`, `lda $D762,y` at `$DBEA`, `$DD30`, `$DED0`, `adc $D762,y` at `$DFC0`, `lda $D78B,y` at `$E073` and `$E7BC`, `lda $D794,y` at `$E06E` and `$E7C1` | | `$D918-$D91B` | 4 | `ObjectXOffsetLowBytes` (`$D918`), `ObjectXOffsetHighBytes` (`$D91A`) | two parallel 2-byte arrays of signed offsets: the low bytes (`$F8 $08`) and the high bytes (`$00 $05`), added to a sprite's X coordinate | `lda $D918,x` at `$D96E`, `lda $D91A,x` at `$D934` | | `$D966-$D966` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$D967` | not referenced by any instruction | | `$D9C4-$D9CB` | 8 | `SpriteAttributeMaskTable` | 8 x 8-bit masks (`$00 $40 $C0 $80 $40 $00 $80 $C0`) OR-ed into a sprite's attribute byte after its low two bits have been masked out; the index is the sprite's own number | `ora $D9C4,x` at `$DA17` | | `$DA97-$DA98` | 2 | `EnemyPointerLowByteTable` | 2 x 8-bit pointer low bytes (`$FC`, `$0C`), loaded into `Zp00` after a call that tests whether the enemy is alive; the index is the enemy's state byte `Zp62,x` | `lda $DA97,y` at `$DAAD` and `$DDDC` | | `$DB4A-$DB4A` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DB4B` | not referenced by any instruction | | `$DC33-$DC33` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DC34` | not referenced by any instruction | | `$DC8F-$DC96` | 8 | `IceBlockPatternPointerTable` | 4 x 16-bit little-endian pointers into the table at `$D75E` (`$D76C`, `$D775`, `$D78A`, `$D793`), copied into the zero-page pointer bytes by the loop at `$DCFE` | `lda $DC8F,y` at `$DCFE` | | `$DD0C-$DD15` | 10 | `PlatformHeightThresholdTable` | 10 x 8-bit height thresholds (`$18 $18 $18 $16 $16 $16 $14 $14 $14 $14`): used as the limit of a countdown, as the value written to `$0303`, and subtracted from `$18` to give a distance | `lda $DD0C,x` at `$DD41`, `cmp $DD0C,x` at `$DD5C`, `sbc $DD0C,x` at `$DDA4` | | `$DD8E-$DD8E` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DD8F` | not referenced by any instruction | | `$DE7A-$DE7A` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DE7B` | not referenced by any instruction | | `$DE8F-$DE8F` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DE90` | not referenced by any instruction | | `$DEB8-$DEB8` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$DEB9` | not referenced by any instruction | | `$DEF6-$DFA4` | 175 | `IceBlockRowXTable` (`$DEF6`), `IceBlockRowPointerLowBytes` (`$DEFA`), `IceBlockRowPointerHighBytes` (`$DEFB`), `IceBlockPositionTable` (`$DF1B`) | the level layout. `$DEF6-$DEF9` = 4 X positions of the ice-block rows (`$50 $80 $B0 $E0`). `$DEFA-$DEFF` = two parallel 3-byte arrays of pointer bytes, giving `$8167`, `$8970` and `$8589`. `$DF1B-$DFA4` = 138 x 8-bit packed ice-block positions: the low five bits are the column (0-31) and the high nibble the row (0-15), read with `and #$1F` and four `lsr a` | `lda $DEF6,y` at `$E031`, `lda $DEFA,y` at `$DFED`, `lda $DEFB,y` at `$DFF2`, `lda $DF1B,x` at `$DFBA`, `$DFF8`, `$E0AA` and `lda $DF1B,y` at `$E01D`, `$E039`, `$E046` | | `$E12B-$E12B` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E12C` | not referenced by any instruction | | `$E185-$E188` | 4 | `ObjectVariantOffsetTable` | 4 x 8-bit offsets (`$28 $58 $88 $B8`) selected by the difference of two zero-page counters, stored into `ZpA8` | `lda $E185,x` at `$E1D9` | | `$E273-$E276` | 4 | `SpriteColumnXTable` | 4 x 8-bit X positions (`$00 $18 $30 $48`, 24 pixels apart) used as an index into the sprite array at `$02A0` | `lda $E273,x` at `$E27C`, `lda $E273,y` at `$E610` | | `$E279-$E279` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E27A` | not referenced by any instruction | | `$E338-$E33B` | 4 | `SpriteRowYTable` | 4 x 8-bit Y positions (`$14 $2C $44 $5C`, 24 pixels apart) used as an index into the sprite array at `$02A0` | `lda $E338,x` at `$E348` | | `$E382-$E4C1` | 320 | `ObjectRowAttributeTable` (`$E382`), `ObjectParamByteTable1` (`$E462`), `ObjectParamByteTable2` (`$E472`), `ObjectParamByteTable3` (`$E482`), `ObjectParamByteTable4` (`$E492`), `ObjectPositionPairTable` (`$E4A2`), `ObjectPointerHighByteTable` (`$E4B2`) | a mixture. `$E382-$E389` = 8 zero bytes read as a row-attribute table. `$E38A-$E461` (216 bytes) is a run whose layout is **not established**: no instruction names an address inside it and the CDL marks only 11 of the 320 bytes as read. `$E462-$E4A1` = four parallel 16-byte tables whose four bytes are assembled into one 32-bit value (`$E462` << 24 \| `$E472` << 16 \| `$E482` << 8 \| `$E492`) before being shifted right four bits by the bit-reader at `$E74B`. `$E4A2-$E4B1` = 8 x 16-bit little-endian (X,Y) position pairs, read one byte at a time with a stride of two. `$E4B2-$E4C1` = 16 x 8-bit bytes, of which the first ten are pointer high bytes | `lda $E382,x` at `$E4CE`, `lda $E4A2,x` at `$E4EF` and `$E67E`, `lda $E4A3,x` at `$E4F5` and `$E683`, `lda $E4B2,x` at `$E668`, `lda $E4BB,x` at `$E6DE`, `lda $E4B7,x` at `$E6FE`, `lda $E462,y` at `$E740`, `lda $E472,y` at `$E73B`, `lda $E482,y` at `$E736`, `lda $E492,y` at `$E731` | | `$E539-$E572` | 58 | `ObjectTypeByteTable` | `$E539-$E55A` (34 bytes) = a table of 8-bit object-type parameters, read with an index that is the type's own slot number and whose low six bits are compared with a limit; `$E55B-$E572` (24 bytes) = unreached code | `lda $E539,x` at `$E575` | | `$E5C7-$E5C7` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E5C8` | not referenced by any instruction | | `$E5FE-$E5FE` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E5FF` | not referenced by any instruction | | `$E727-$E72A` | 4 | `ObjectOffsetTable` | 4 x 8-bit offsets (`$D3 $8C $CD $8F`), indexed by a value shifted right twice and masked to two bits | `lda $E727,x` at `$E775` | | `$E778-$E778` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E779` | not referenced by any instruction | | `$E77B-$E77B` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$E77C` | not referenced by any instruction | | `$E78D-$E79C` | 16 | `SpriteBufferOffsetTable` | 16 x 8-bit offsets into the sprite buffer at `$0600` (`$21 $4E $20 $4D $42 $42 $42 $42 $42 $48 $4E $9D $9D $9D $9D $9D`), used as an X index when a sprite is written to the buffer | `ldx $E78D,y` at `$E81C`, `lda $E790,x` at `$E7E0` | | `$E88C-$E998` | 269 | `EnemySpriteTileTable` (`$E8C3`), `EnemySpritePaletteTable` (`$E8C7`), `EnemySpritePointerTable` (`$E8CB`), `EnemySpriteSpeedTable` (`$E8CF`), `EnemySpriteDataTable` (`$E8DE`) | the enemy sprite definitions. `$E8C3`, `$E8C7`, `$E8CB` and `$E8CF` are four parallel 4-byte tables (tile, palette, pointer byte and speed) read with the enemy's type as the index; `$E8DE-$E8F3` is a 22-byte table of enemy data returned by the routine at `$EA55`. The rest of the block - `$E88C-$E8C2` and `$E8F4-$E998` - is **not established**: no instruction names an address inside it and the CDL never touched it | `lda $E8C3,x` at `$E9BA`, `lda $E8C7,x` at `$E9AC`, `lda $E8CB,x` at `$E9A0`, `lda $E8CF,x` at `$E9B3`, `lda $E8DE,y` at `$EA58` | | `$E9CE-$EA48` | 123 | - | unreached **code**: the 123 bytes decode as 6502 instructions throughout (`LDA $0360,Y; ADC $0365,X; ...`) | not referenced by any instruction | | `$EA5C-$EA8D` | 50 | - | unreached **code**: the 50 bytes decode as 6502 instructions throughout | not referenced by any instruction | | `$EAA2-$EAFB` | 90 | `EnemySpriteDefinitionTable` (`$EAA2`), `EnemySpritePlane0Rows` (`$EABF`), `EnemySpritePlane1Rows` (`$EADD`) | the enemy graphics. `$EAA2-$EABE` = 29 x 8-bit offsets, one per enemy type, read with the enemy's type as the index. `$EABF-$EADC` and `$EADD-$EAFB` = two runs of 8x8 2bpp tile rows - the bytes are the bit patterns `$55`, `$AA`, `$FF`, `$00`, `$22`, `$32`, `$42`, `$11`, `$21`, `$24` - read with the same index and used as the two bit planes of the enemy's sprite | `lda $EAA2,x` at `$EB9C` and `$EC48`, `lda $EABF,y` at `$EB03`, `lda $EADD,y` at `$EB0E` | | `$EB14-$EB2D` | 26 | - | unreferenced data: 26 x 8-bit values in `$01`-`$08` (fourteen of them are `$04`), the shape of a per-enemy-type speed or step table. No instruction in this dump names an address inside the block and the CDL read only four of the bytes, so the index and the units are not established | not referenced by any instruction | | `$EB55-$EB55` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$EB56` | not referenced by any instruction | | `$EB61-$EB61` | 1 | - | single `$24` byte: the opcode of a two-byte `BIT zp` whose operand byte is the first byte of the routine at `$EB62` | not referenced by any instruction | | `$ECB7-$ECBE` | 8 | - | unreached **code**: two complete `JSR` instructions (`JSR $30F0`, `JSR $1DC0`) | not referenced by any instruction | | `$ECE9-$ECE9` | 1 | - | single `$85` byte: the opcode of a two-byte `STA zp` whose operand byte is the first byte of the routine at `$ECEA` | not referenced by any instruction | | `$ED09-$ED24` | 28 | - | unreached **code**: the 28 bytes decode as 6502 instructions throughout (`LDY $43; STY $D0; ...`) | not referenced by any instruction | | `$ED68-$EDBE` | 87 | - | unreached **code**: the bytes decode as 6502 instructions throughout and the CDL marks 77 of the 87 bytes as executed code | not referenced by any instruction | | `$EDD0-$EDD4` | 5 | - | unreached **code**: the bytes decode as `LDA $5A; BIT $5B; LDA $5B` | not referenced by any instruction | | `$EDE6-$EE0C` | 39 | `EnemyBehaviourParameterTable` (`$EDE6`), `EnemyAttackAngleTable` (`$EDED`), `EnemySpeedTable` (`$EDF1`) | the enemy behaviour parameters. `$EDE6-$EE0C` is one run of 39 parameter bytes read from four overlapping bases: `$EDED` (8 x 8-bit attack timers, `$68 $98 $C8 $F0 $18 $20 $28 $20`, compared against an enemy's timer), `$EDF1` (12 x 8-bit values, indexed by a running counter plus a per-enemy offset), `$EDFD` (9 x 8-bit values stored into the per-enemy field at `$03DD`) and `$EE00` (13 x 8-bit values added to the enemy's Y position) | `lda $EDFD,y` at `$EEB5`, `lda $EDED,y` at `$EEDD`, `lda $EE00,y` at `$EF26`, `lda $EDF1,y` at `$EF96` | | `$EFE2-$EFEB` | 10 | `ObjectSpawnXTable` | 10 x 8-bit X positions; the eight entries at `$EFE4-$EFEB` (`$30 $40 $40 $50 $60 $70 $80 $90`, 16 pixels apart) are read with an index masked to three bits and stored into the per-enemy field at `$03A6`; `$EFE2-$EFE3` are unreferenced | `lda $EFE4,y` at `$F0B1` | | `$F02F-$F03A` | 12 | `SoundDurationTable` | 12 x 8-bit values in `$01`-`$07`, read with an index computed from a sound value and the pointer byte `Zp00`; the code special-cases the twelfth entry against `$07` | `lda $F02F,y` at `$F063` | | `$F107-$F10A` | 4 | - | unreferenced data: four bytes (`$10 $0E $0C $0C`) that look like a short step or speed table, but no instruction in this dump names an address inside the block and the CDL never touched it, so the index and the units are not established | not referenced by any instruction | | `$F149-$F19D` | 85 | - | unreached **code**: the bytes decode as instructions (the head uses the undocumented `$CF` opcode), and the CDL never touched the block | not referenced by any instruction | | `$F1AC-$F2EB` | 320 | `MusicSequenceData` (`$F1AC`), `MusicNoteDurationTable` (`$F267`), `MusicNoteParameterTable` (`$F291`), `MusicChannelParameterTable` (`$F2E4`) | the music data. `$F267-$F28F` = 41 x 8-bit note values in a rising sequence (`$01` ... `$2E`, then `$00`); `$F291-$F2B9` = 41 x 8-bit per-note parameters; `$F2E4-$F2EB` = 8 x 8-bit per-channel parameters, indexed by the channel number masked to three bits. `$F1AC-$F266` and `$F2BA-$F2E3` are the rest of the sequence data; their finer layout is not established | `cmp $F22F,x` at `$F33A`, `lda $F238,x` at `$F34E`, `lda $F25E,x` at `$F34A`, `lda $F2BA,y` at `$F3D5`, `lda $F267,y` at `$F3DD`, `lda $F291,y` at `$F3E4`, `lda $F2E4,x` at `$F3AB` | | `$F3C5-$F3C5` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$F3C6` | not referenced by any instruction | | `$F45A-$F45A` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$F45B` | not referenced by any instruction | | `$F466-$F503` | 158 | `MusicChannelNoteTable` (`$F46C`) | `$F466-$F46B` = 6 bytes whose layout is not established; `$F46C-$F503` = a table of music-channel data, indexed by the channel number `Zp6A,x` and stored into the per-channel field `Zp37,x`. The rest of the block decodes as instructions and the CDL never touched it, so the table/data boundary inside the block is not established | `lda $F46C,y` at `$F506` | | `$F51C-$F523` | 8 | `ScreenBufferInitData` | `$F51C-$F51D` = 2 unreferenced bytes; `$F51E-$F523` = 6 x 8-bit bytes copied into the nametable-copy buffer at `$0400` by the loop at `$F52D` | `lda $F51E,x` at `$F52D` | | `$F538-$F563` | 44 | `MusicChannelInitBytes` | `$F538-$F53A` = 3 x 8-bit bytes copied into the sprite buffer at `$0301`; `$F53B-$F563` is unreached code (the CDL marks `$F53E` as a jump target) | `lda $F538,y` at `$F566` | | `$F582-$F585` | 4 | - | unreferenced data: 4 bytes (`$00 $05 $00 $0E`) of which the two at `$F583-$F584` are added to `$48` to form a value stored in the sprite buffer; the index and the units are not established | `adc $F583,x` at `$F592` | | `$F5C9-$F62F` | 103 | `MusicNoteSequenceTable` (`$F5D3`), `MusicChannelEnvelopeTable` (`$F5F9`), `MusicChannelInitTable` (`$F609`) | the per-channel music parameters. `$F5D3-$F5F8` = 38 x 8-bit note/frequency bytes; `$F5F9-$F608` = 16 x 8-bit envelope bytes; `$F609-$F614` = 6 x 8-bit initialisation bytes. `$F5C9-$F5D2` and `$F615-$F62F` are unreached code | `lda $F5D3,y` at `$F660`, `lda $F5F9,x` at `$F693`, `lda $F609,y` at `$F671` | | `$F63B-$F64B` | 17 | - | unreached **code**: the bytes decode as instructions throughout and the CDL marks 13 of the 17 bytes as executed code | not referenced by any instruction | | `$F67E-$F690` | 19 | - | unreached **code**: the bytes decode as instructions throughout and the CDL marks all 19 bytes as executed code | not referenced by any instruction | | `$F6EF-$F71C` | 46 | - | unreached **code**: the bytes decode as instructions throughout (`LDY #$30; CPY #$A2; LDA #$00; STA $91,X; ...`) | not referenced by any instruction | | `$F730-$F736` | 7 | `SoundEffectPointerLowBytes` (`$F730`), `SoundEffectTileTable` (`$F733`) | two parallel 3-byte arrays: `$F730-$F732` (`$3E $46 $46`) are pointer low bytes loaded into `Zp02`; `$F733-$F735` (`$50 $51 $4E`) are tile bytes written into the sprite array at `$0241` | `lda $F730,y` at `$F751`, `lda $F733,x` at `$F77D` | | `$F7B8-$F80B` | 84 | `ObjectCounterInitTable` (`$F7B8`), `ObjectBehaviourStepTable` (`$F7BC`), `MusicChannelInitValues` (`$F7E8`), `ObjectTypeCountTable` (`$F7EC`) | the object behaviour table. `$F7B8-$F7BB` = 4 x 8-bit initial counter values, stored into `$03BC`. `$F7BC-$F7DF` = 36 x 8-bit behaviour bytes in four groups of eight, each group terminated by `$FF`; the counter `$03BC` indexes them and bit 2 and the value `$FF` are tested. `$F7E8-$F7EB` = 4 x 8-bit values (`$10 $F0 $10 $F0`). `$F7EC-$F803` = 24 x 8-bit counts in `$00`-`$03` | `lda $F7B8,y` at `$F81A`, `lda $F7BC,y` at `$F8E8` and `$F909`, `lda $F7E8,y` at `$F815`, `lda $F7EC,y` at `$F8AF` | | `$F8CA-$F8CA` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$F8CB` | not referenced by any instruction | | `$F910-$F910` | 1 | - | single `$2C` byte: the opcode of a `BIT abs` whose operand bytes are the first two bytes of the routine at `$F911` | not referenced by any instruction | | `$F91C-$F91D` | 2 | - | unreferenced data: two bytes of `$FF`. No instruction in this dump names an address inside the block and the CDL never touched it | not referenced by any instruction | | `$F993-$F9AA` | 24 | `NoteControlTable` (`$F993`), `NotePeriodTable` (`$F99F`) | two parallel 12-byte tables of the music engine's note data. `$F99F-$F9AA` = the 12 note periods, written straight into the APU pulse-1 timer (`$4002`/`$4003`); `$F993-$F99E` = 12 control bytes, used as an X index by the routine the note handler jumps to | `lda $F99F,y` at `$F9B6`, `ldx $F993,y` at `$F9C1` | | `$FA63-$FA63` | 1 | - | a single `$1A` byte that the CDL marks as read as data at run time; no instruction in this dump names an address inside the block and the layout of the one byte is not established | not referenced by any instruction | | `$FB3B-$FB70` | 54 | `SoundEffectParameterTable` (`$FB3B`), `SoundEffectChannelOffsetTable` (`$FB4B`), `NoiseEffectParameterTable` (`$FB52`), `NoiseEffectChannelOffsetTable` (`$FB62`), `NoiseEffectLengthTable` (`$FB6A`), `NoiseEffectAltOffsetTable` (`$FB6E`) | the sound-effect parameter tables. `$FB3B-$FB4A` = 16 x 8-bit parameters indexed by the sound id masked to four bits; `$FB4B-$FB52` = 8 x 8-bit channel offsets indexed by the sound id shifted right three; `$FB52-$FB61` = 16 x 8-bit noise parameters; `$FB62-$FB69` = 8 x 8-bit noise channel offsets; `$FB6A-$FB6D` = 4 x 8-bit noise lengths; `$FB6E-$FB70` = 3 x 8-bit offsets. All of them feed the routine at `$FB8C`, which writes the APU noise registers | `ldx $FB4B,y` at `$FB80`, `lda $FB3B,y` at `$FB89`, `ldx $FB62,y` at `$FBD0`, `lda $FB52,y` at `$FBD6`, `ldx $FB6E,y` at `$FBF0`, `lda $FB6A,y` at `$FBF6` | | `$FD22-$FFFF` | 734 | `SoundEngineData` (`$FD22`), `ApuPulse2ControlTable` (`$FFDB`), `CpuVectors` (`$FFFA`) | the fixed data area of the sound engine plus the CPU vectors. `$FD22-$FD29` = 8 x 8-bit offsets into the block, one per bit count: the routine at `$FC1B` reads the offset with `lda $FD21,y` (the byte before the block is the `RTS` of the routine at `$FD21`, so the offsets are the block's own first eight bytes) and then reads a 6-byte record - five bytes into the zero page and one into `$0705` - that describes a sprite. `$FD2A-$FFDA` = the sound engine's data blob: the CDL marks 618 of the 734 bytes as read at run time, but the individual tables inside it are not separated by the code, so its layout is not established. `$FFDB-$FFF8` = 30 x 8-bit APU pulse-2 control bytes (`$50`-`$57`, i.e. the eight duty settings at a constant volume of 1) written to `$4004`. `$FFFA-$FFFF` = the three CPU vectors (NMI `$C076`, RESET `$C014`, IRQ `$FFF0`) | `lda $FD21,y` at `$FC1B`, `lda $FD22,y` at `$FC1F`, `lda $FD23,y` at `$FC24`, `lda $FD24,y` at `$FC29`, `lda $FD25,y` at `$FC2E`, `lda $FD26,y` at `$FC33`, `lda $FD27,y` at `$FC38`, `lda $FFD9,y` at `$FCBE` | ### Notes on the blocks that are not tables - **Thirty-four one-byte regions** hold a single opcode byte that the CPU really executes: thirty-one of them hold `$2C` (`BIT abs`), `$EB61` holds `$24` (`BIT zp`), `$ECE9` holds `$85` (`STA zp`) and `$FA63` holds `$1A`, which the CDL marks as data. The tracer's split could not attribute the byte, so it is emitted as a data region; the instruction that follows it starts at the same address in both readings, so no byte is misplaced and the build is unaffected. Mesen's own disassembler confirms the reading (`$C5EC` is `BIT $00A9`). - **Twenty-three regions hold code the tracer could not reach**, in whole or in part. They are listed above with the reason: most of them are the bodies of the routines the dispatch tables at `$C440` and `$CD20` point at, and the Mesen CDL capture shows the CPU executing them. Seeding the tracer at the first byte of each would turn them back into routines. - **Three regions are data the ROM does not pin down**: `$C5A1-$C5A6` (6 bytes), `$EB14-$EB2D` (26 bytes) and `$F107-$F10A` (4 bytes). Their byte patterns are those of small per-type value tables, but no instruction in this dump names an address inside them and the CDL only read a handful of bytes, so the index and the units are not established. They are left unnamed on purpose. - **Two large runs inside named blocks are not established either**: `$E38A-$E461` (216 bytes, inside the `$E382-$E4C1` block) and the sound-engine blob `$FD2A-$FFDA` (691 bytes, inside the `$FD22-$FFFF` block). Both are recorded with their ROM provenance rather than given a speculative name. - The `ApuPulse2ControlTable` at `$FFDB` is *not* ASCII text, even though `$50`-`$57` are the codes for `P` to `W`: the values are written to the APU pulse-2 control register at `$4004`, where `$50`-`$57` are the eight duty-cycle settings with a constant volume of 1. --- ## Open work - The **routine names** are still `SubC` for everything except the vector entries and a handful of `Update*`/`Init*` routines; the game's roles for the rest are not established from the code alone. - The **zero-page aliases** in `locals.inc` are mostly the generated `ZpXX` names; the roles are not proven from the code alone. - The layout of the **216-byte run `$E38A-$E461`** (inside the `$E382-$E4C1` block) and of the **689-byte sound-engine blob `$FD2A-$FFDA`** (inside the `$FD22-$FFFF` block) is **not established**. Both are recorded with their ROM provenance in the inventory rather than given a speculative name. - Three small blocks - `$C5A1-$C5A6` (6 bytes), `$EB14-$EB2D` (26 bytes) and `$F107-$F10A` (4 bytes) - look like per-type value tables, but no instruction in this dump names an address inside them and the Code Data Logger only read a handful of bytes, so their index and units are not established. They are left unnamed on purpose. - **Twenty-three of the data blocks are code the tracer could not reach.** They stay `.byte` rows; seeding the tracer at the first byte of each would turn them back into routines. - The **34 one-byte blocks** hold an opcode byte that the CPU really executes (`$2C` = `BIT abs` in 31 of them). The tracer's split could not attribute the byte, so it is emitted as data; the instruction that follows it starts at the same address either way, and no byte is misplaced. --- ## Entry-point naming (2026-10-21) Every `jsr`/`jmp` target that carried an address-derived `SubCxxxx` name was reviewed against the routine it points at, the code that calls it and the RAM fields it touches. The same pass was applied to the `(JE)` variant, whose PRG is byte-identical to this one, so the two `prg.s` files stay identical. | | count | |---|---| | Address-derived entry points before | 191 | | Entry points now carrying a semantic name | **138** | | Entry points still address-derived | **53** | The names now in `prg.s` cover: * **The sound engine** — `UpdateSoundEngine`, `UpdateSoundPulse1`, `UpdateSoundPulse2`, `UpdateSoundNoise`, `SetPulse1Tone`, `SetPulse2Tone`, `SetPulse1Timbre`, `SetPulse1TimbreLow`, `SetPulse1TimbreAlt`, `SetPulse2Timbre`, `GetSoundEngineValue`, `ResetPulse1Voice`, `SilencePulse1`, `CheckSoundIdle`, `UpdateSoundForObject`. * **The objects on the ice** — `UpdateObject`, `UpdateObjectByType`, `UpdateObjectSprite`, `UpdateObjectAndSprite`, `SetObjectTile`, `SetObjectTileAndAttribute`, `SetObjectTileAlt`, `SetObjectType`, `SetObjectPosition`, `SetObjectDirection`, `SetObjectColour`, `SetObjectTileIndex`, `SetObjectSpriteIndex`, `SetObjectFlagsPointer`, `GetObjectFlags`, `GetObjectTileOffset`, `GetObjectTileOffsetAlt`, `GetObjectTileOffsetC`, `GetObjectAttributeOffset`, `GetObjectSpriteTile`, `GetObjectSpritePointer`, `GetObjectBehaviour`, `GetObjectCounter`, `AddObjectCounter`, `AddObjectCounterAlt`, `AdvanceObjectCounter`, `TickObjectCounter`, `TickObjectTimers`, `TickObjectDelay`, `TickObjectAnimation`, `GetObjectAnimationState`, `GetObjectAnimationMask`, `ResetObjectState`, `InitObjectState`, `InitObjectSprite`, `DrawObjectSprite`, `WriteObjectToOam`, `WriteObjectToOamAlt`, `LoadObjectSprite`, `SpawnObject`, `ClampObjectIndex`. * **The player and the enemies** — `MovePlayer`, `MoveEnemy`, `UpdatePlayerObject`, `UpdateSecondPlayerObject`, `UpdatePlayerObjectAlt`, `UpdatePlayerSprites`, `UpdatePlayerSprite`, `UpdatePlayerSpriteAlt`, `UpdatePlayerMovement`, `CheckPlayerCollision`, `CheckObjectCollision`, `CheckObjectTile`, `CheckObjectInRange`, `CheckObjectOnScreen`, `CheckObjectOnScreenAlt`, `CheckObjectFlags`. * **The board and the scrolling** — `UpdateLevelScroll`, `ScrollLevelLeft`, `ScrollLevelRight`, `ScrollLevelIfNeeded`, `CheckScrollPosition`, `GetScrollRow`, `AdvanceScrollColumn`, `UpdateBoardState`, `ResetBoardState`, `SetObjectMapEntry`, `WriteObjectMapEntry`, `GetObjectMapEntry`, `PlaceObjectOnBoard`, `ConvertObjectPosition`, `SplitObjectPosition`, `ComputeObjectRow`, `ComputeObjectHeight`, `ComputeObjectDistance`, `ComputeObjectReach`, `GetObjectReach`, `MoveObject`, `MoveObjectUp`, `MoveObjectUpStep`, `MoveObjectAndPlaySound`, `AddObjectXOffset`, `AddObjectOffset`, `ToggleObjectFlag`, `SaveObjectPointer`, `UpdateObjectAnimation`, `StartObjectAnimation`. * **Small helpers** — `Negate`, `GetRandomIndex`, `CompareRandomPair`, `CheckRandomPair`, `GetTableIndex`, `SetInputState`, `ReadPlayerStat`, `UpdateSprites`, `UpdateSpritePosition`, `ReadMovementTable`, `ReadBehaviourTable`, `CheckStartButton`, `CheckExpansionInput`, `TickGameTimer`. **Residue — 53 entry points remain address-derived.** They are the one-instruction fragments of the object's state machine: helpers that load a constant into X or Y, branch on a flag the dump does not explain, or select one of the game's per-player slots. The dump does not reveal a role that would distinguish them from their neighbours, so they keep their address-derived names. They are `SubCCE88`, `SubCCEDB`, `SubCD021`, `SubCD0C5`, `SubCD111`, `SubCD1E5`, `SubCD1F9`, `SubCD2F9`, `SubCD421`, `SubCD463`, `SubCD493`, `SubCD507`, `SubCD6E0`, `SubCD724`, `SubCD890`, `SubCD9AD`, `SubCD9CC`, `SubCDA4B`, `SubCDAB2`, `SubCDB1D`, `SubCDBB0`, `SubCDBB4`, `SubCDDBD`, `SubCDEEA`, `SubCE002`, `SubCE05E`, `SubCE277`, `SubCE27A`, `SubCE29A`, `SubCE321`, `SubCE33C`, `SubCE523`, `SubCE63D`, `SubCE79D`, `SubCEA4A`, `SubCEB2E`, `SubCEB5F`, `SubCEC51`, `SubCECBF`, `SubCED63`, `SubCEE0D`, `SubCEEA2`, `SubCEF06`, `SubCEF42`, `SubCEF9A`, `SubCF10B`, `SubCF13B`, `SubCF35B`, `SubCF3BD`, `SubCF564`, `SubCF71D`, `SubCF8DB`, `SubCF964`.