# Ice Hockey (U) ## Status **Pass 1, with the Pass 3 data-table naming applied.** The project builds and verifies byte-identical to the original ROM. `prg.s` is a traced disassembly classifying bytes as reachable code or data, with every instruction carrying a trailing audit comment. Unreached bytes remain as explicit `.byte` blocks; no ROM byte is deleted or reformatted. The data blocks have been interpreted and named - see [Pass 3 data-table inventory](#pass-3-data-table-inventory) below. The *code* labels have been given semantic names for every `jsr`/`jmp` entry point whose role the ROM reveals - see [Address-derived entry points (residue)](#address-derived-entry-points-residue) below for the ones that are still address-derived. The routine headers are still the generated boilerplate, so the routine-comment part of Pass 3 is still to do. `make` assembles `header.s`, `prg.s`, `chr.s`, links them with `ld65` and compares the result against `rom.nes`: ``` Byte-identical ROM verified. ``` ## ROM | Property | Value | |----------|-------| | Format | iNES | | Mapper | 0 | | Board | NROM-256 | | Mirroring | Vertical | | Four-screen VRAM | no | | Battery | no | | PRG | 32 KB (2 x 16 KB), mapped at CPU $8000-$FFFF | | CHR | 8 KB (1 x 8 KB) | | Trainer | none | | Footer | none | | NMI vector | $805A | | RESET vector | $8000 | | IRQ vector | $8000 | | ROM file | `Ice Hockey (U).nes` | | SHA-256 | `3ea8ec4486efc6c925704177505b957895171066cea2d4d80527798b23330bd3` | ### Header notes - none ## 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 `$8000` (`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 | |---------|---------| | `$800B` | `SubC800B` (falls through) | | `$8010` | `SubC8010` (falls through) | | `$8159` | `ClearNametableTiles` | | `$8111` | `ClearGameMemory` | | `$8050` | `SubC8050` (falls through) | | `$8167` | `SubC8167` (falls through) | | `$8113` | `SubC8113` (falls through) | | `$8050` | `SubC8050` | **NMI** is at `$805A` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$810F` | `NmiQuickExit` | **IRQ** is at `$8000` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path. ## Pass 3 data-table inventory `prg.s` classifies every PRG byte as either an instruction or as data. This section lists every `; ---- data $XXXX-$YYYY` block in the project, what the bytes in it are, and which instruction in this dump reads them. The static split alone cannot tell a table from unreachable code, so the classification below uses the project's own Mesen capture, `icehockey_gameplay.cdl`, as a second opinion. Mesen's Code Data Logger marks each PRG byte it fetched as an opcode (Code), each byte it read as data (Data), and each byte it executed (Exec). Where the CDL says "Code" and `prg.s` says data, the block is code the tracer could not reach; where it says "Data", the bytes really are tables. Names are the game's own concepts where the ROM supports them. Where it does not, the name says what the bytes *are* (a pointer table, an OAM tile-index list, an 8.8 heading) rather than guessing at a game term, and the "Structure" column records the evidence. Where a name carries a letter suffix (`...TableA`, `...TableB`) the two or more tables are identical in shape and differ only in the data they hold, so the ROM does not support telling them apart by name. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$8059-$8059` | 1 | - | One `$00` byte: a pad between the reset stub's `jmp SubC8050` at `$8056` and the NMI handler at `$805A`. Not a table. | not referenced by any instruction | | `$8213-$821A` | 8 | `ScreenLayoutPointerTable` | 4 x 16-bit little-endian pointers: `$DCF4`, `$DE0D`, `$DFA0`, `$E1D5`. Each points at the head of an RLE-compressed nametable stream inside `CompressedScreenData`; the index is selected by a 2-bit value tested with `ror` at `$81D2` (Y = 0, 2, 4, 6). | `lda $8213,y` at `$81E6`, `lda $8214,y` at `$81EB` (loaded into the zero-page pointer `Zp0A`/`Zp0B`) | | `$82D6-$82E5` | 16 | `NametableTilePatternTable` | 16 x 8-bit CHR tile indices in four groups of four (`$82D6`/`$82D7`, `$82D8`/`$82D9`, `$82DA`/`$82DB`, `$82DC`/`$82DD`). The two fill loops write two bytes per iteration to PPUDATA at nametable address `$25C4` and `$25E4`, alternating between two of the four groups (Y is toggled with `eor #$04`). | `lda $82D6,y` at `$82A3`, `lda $82D7,y` at `$82A9`, `lda $82D8,y` at `$82C2`, `lda $82D9,y` at `$82C8` | | `$8371-$8375` | 5 | - | Not a table: `inc $08` / `jmp $8378`, i.e. the tail of the dispatch routine. The CDL marks all five bytes as Code and one as Exec, so the tracer's split is wrong here and the bytes are code. | executed as code at `$8371` | | `$8390-$83E0` | 81 | `SpriteRoutinePointerTable` (`$8390`), `SpriteJumpTablePointerTable` (`$83A6`) | Two 4-entry 16-bit pointer tables plus code. `$8390`: `$83BC`, `$8B0E`, `$8CB1`, `$9384` (routine entry points). `$83A6`: `$83F3`, `$8B12`, `$8CFB`, `$93CE` - the bases of the per-state jump tables, so `GameStateJumpTable` and the three tables it mirrors. `$83B0-$83E0` is code. | `$83A6`/`$83A7` by `lda $83A6,x` at `$8381` and `lda $83A7,x` at `$8386`; `$8390` is read at run time (CDL Data) by code the tracer did not reach | | `$83F3-$8516` | 292 | `GameStateJumpTable` (`$83F3`) | The first 30 bytes (`$83F3-$8410`) are 15 x 16-bit little-endian jump targets: `$8411`, `$842B`, `$8440`, `$847D`, `$8494`, `$852D`, `$8549`, `$8574`, `$8591`, `$85C7`, `$8807`, `$881D`, `$88B0`, `$8902`, `$8AF8`. The rest of the block (`$8411-$8516`) is the code the targets point at. | read indirectly: `Zp0A`/`Zp0B` are set to `$83F3` from `SpriteJumpTablePointerTable`, then `lda (Zp0A),y` at `$8361`/`$8365` fetches the target and `jmp (Zp0A)` at `$836E` jumps to it | | `$852D-$853B` | 15 | - | Not a table: code (`jsr $853C`, `inc $058F`, `lda #$80`, ...). The CDL marks 12 bytes as Code and 2 as Exec. | executed as code at `$852D` | | `$8549-$8600` | 184 | - | Not a table: code. The CDL marks 157 of the 184 bytes as Code and 11 as Exec, so the tracer's split is wrong here. | executed as code at `$8549` | | `$8629-$86BC` | 148 | `RandomNumberRoutinePointerTable` (`$8629`) | The first 32 bytes are 16 x 16-bit little-endian pointers to small helpers at `$8624`, `$8651`, `$8654`, `$8657`, `$8665`, `$8673`, `$8683`, `$8693`, `$869C`, `$86A8`, `$86B1` - a linear congruential generator (`lda $03,x` / `inc $04` / `stx $03`) and increment/decrement counters. The rest of the block (`$8649-$86BC`) is those routines. | no instruction in the traced dump references the table; the CDL shows the block was never touched during the capture | | `$86E2-$86E4` | 3 | `PlayerSpriteTileIndexTable` | 3 x 8-bit CHR tile indices (`$27`, `$9F`, `$BF`), one per object kind (the kind is `Zp05`); the byte becomes the OAM tile index. | `lda $86E2,x` at `$86CD` | | `$879E-$87A3` | 6 | `SpriteStripXPositionTable` (`$879E`) | `$879E-$879F` are two X positions (`$10`, `$38`) for a three-sprite strip drawn at Y = `$78` with tile `$F5`; `$87A0-$87A3` is code. The reader indexes with `x*8`, so only the first entry of the wider table lies inside this block. | `lda $879E,x` at `$8748` and at `$875E` | | `$87E3-$87E7` | 5 | `SpriteXPositionTable` (`$87E3`) | `$87E3` is one X position (`$60`) for a single sprite drawn at Y = `$9F`; `$87E4-$87E7` is code. | `lda $87E3,x` at `$87D4` | | `$8804-$8842` | 63 | `SpriteStripXPositionTableB` (`$8804`) | `$8804` is one X position (`$60`) for a three-sprite strip drawn at Y = `$BF`; `$8805-$8842` is code. | `lda $8804,x` at `$87F5` | | `$8898-$88ED` | 86 | `PlayerDirectionAngleTableA` (`$8898`), `...B` (`$889E`), `...C` (`$88A4`), `...D` (`$88AA`) | 16 bytes in four 4-byte groups at stride 6 (`$8898`, `$889E`, `$88A4`, `$88AA`). Each 2-byte entry is a little-endian 8.8 fixed-point heading: the low byte is the fraction and the high byte the heading (`$E8`-`$EC`). The rest of the block is code. | `lda $8898,y` at `$884E` and `lda $8899,y` at `$8854`; `lda $889E,y` at `$8861` and `lda $889F,y` at `$8867`; `lda $88A4,y` at `$8876` and `lda $88A5,y` at `$887C`; `lda $88AA,y` at `$888B` and `lda $88AB,y` at `$8891` - all stored to `$0534`/`$0535` | | `$8902-$8963` | 98 | - | Not a table: code. The CDL marks 79 of the 98 bytes as Code. | executed as code at `$8902` | | `$8985-$8994` | 16 | `PlayerStartYPositionTable` (`$8985`), `PlayerStartXPositionTable` (`$898D`) | Two 8-entry tables. Y = `$60`, `$88`, `$A0`, `$CC` twice over; X = `$44`, `$6C`, `$1C`, `$44` for the left-hand side and `$BC`, `$94`, `$E4`, `$BC` for the right-hand side - the second group is the mirror of the first (`$FF - $44 = $BB` ~ `$BC`). Four starting rows x two teams. | `lda $8985,x` at `$8964` -> `Zp0A` (base Y), `lda $898D,x` at `$8969` -> `Zp0B` (base X) | | `$8A1C-$8A2A` | 15 | `PlayerRoleOffsetXTableA` (`$8A1C`), `PlayerRoleOffsetXTableB` (`$8A21`), `PlayerRoleOffsetYTable` (`$8A26`) | Three 5-entry tables indexed by the player's role (0-4). `$8A1C` = `$6F 7F 97 A7 7F` and `$8A21` = `$6F 97 7F A7 7F` are alternative X offsets (the carry flag chooses between them); `$8A26` = `$00 10 F0 00 00` is a further offset. All three are added to the sprite's screen position before the OAM write. | `lda $8A1C,x` at `$89A8`, `lda $8A21,x` at `$89AD`, `lda $8A26,x` at `$89B2` | | `$8A65-$8A67` | 3 | `PlayerSlotRemapTableA` | 3 x 8-bit slot numbers (`$01`, `$02`, `$00`) - a permutation of 0, 1, 2 used to re-index the player-slot array `$0358`. | `lda $8A65,y` at `$8A5C` | | `$8A86-$8A88` | 3 | `PlayerSlotRemapTableB` | 3 x 8-bit slot numbers (`$02`, `$00`, `$01`) - the other permutation of 0, 1, 2. | `lda $8A86,y` at `$8A7D` | | `$8AA2-$8AA6` | 5 | `PlayerRoleRemapTableA` | 5 x 8-bit values (`$04 03 03 03 03`), indexed by the current role; the result is written back over the role byte in `$03B0`. | `lda $8AA2,x` at `$8A9C` | | `$8AB1-$8AB5` | 5 | `PlayerRoleRemapTableB` | 5 x 8-bit values (`$00 00 00 04 00`), same role-remap shape as `PlayerRoleRemapTableA`. | `lda $8AB1,x` at `$8AAB` | | `$8AC0-$8AC4` | 5 | `PlayerRoleRemapTableC` | 5 x 8-bit values (`$02 04 02 02 02`), same role-remap shape. | `lda $8AC0,x` at `$8ABA` | | `$8AD3-$8AD7` | 5 | `PlayerRoleRemapTableD` | 5 x 8-bit values (`$01 01 04 01 01`), same role-remap shape. | `lda $8AD3,x` at `$8AC9` | | `$8AF8-$8B4B` | 84 | `PlayerEventRoutinePointerTable` (`$8B12`) | `$8B12-$8B1F` is 7 x 16-bit little-endian pointers (`$8B20`, `$8B34`, `$8B60`, `$8BA9`, `$8BED`, `$8C4E`, `$8C9A`); `$8AF8-$8B11` and `$8B20-$8B4B` are code. | read at run time (CDL Data) by code the tracer did not reach | | `$8B60-$8CF1` | 402 | `RinkNametableTileDataA` (`$8C7E`), `RinkNametableTileDataB` (`$8C90`) | `$8C7E` (18 bytes) and `$8C90` (10 bytes) are CHR tile indices that are copied into the nametable staging buffers `$0413` and `$0425`; `$8C3E-$8C4D` is a further 16 bytes of packed tile/attribute nibbles (`$50 70 90 B0 ... $40 40 40 40`). The rest of the block is code. | `lda $8C7E,x` at `$8C50` and `lda $8C90,x` at `$8C57` (copied to `$0413` / `$0425`) | | `$8CFB-$8D8E` | 148 | `PlayerObjectRoutinePointerTable` (`$8CFB`) | The first 26 bytes are 13 x 16-bit little-endian pointers (`$8D15`, `$8DBC`, `$8E04`, `$8E9D`, `$8F20`, `$8F46`, `$9074`, `$90F5`, `$919C`, `$91C2`, `$91D5`, `$920C`, `$9237`); `$8D15-$8D8E` is the code they point at. | read at run time (CDL Data at `$8CFB-$8D14`) by code the tracer did not reach | | `$8DAA-$8DE0` | 55 | `PlayerFacingOffsetTable` (`$8DAA`) | `$8DAA-$8DAB` = `$40`, `$80` - two heading bytes; `$8DAC-$8DE0` is code. The heading byte is shifted left one bit and stored in `ZpD1`/`Zp1F`, so the pair is a half-step heading table. | `lda $8DAA,y` at `$8D94` and `lda $8DAB,y` at `$8D9C` | | `$8E04-$8E1F` | 28 | - | Not a table: code (the CDL marks all 28 bytes as Code, 2 as Exec). | executed as code at `$8E04` | | `$8E8D-$8EAF` | 35 | `PlayerStepTableA` (`$8E8D`), `PlayerStepTableB` (`$8E95`) | `$8E8D-$8E90` = `$01 00 02 03` and `$8E95-$8E9C` = `$03 01 02 00 00 02 01 03`: two small 8-bit tables of movement-step selectors. The rest of the block is code. | `lda $8E8D,y` at `$8E78` | | `$8EFC-$8F51` | 86 | `PlayerStepTableC` (`$8EFC`) | `$8EFC-$8F1B` is a 32-byte table of 8-bit step selectors in four 8-byte groups; the rest of the block is code. | `lda $8EFC,y` at `$8ECB` and at `$8EEE` | | `$8F9C-$90CA` | 303 | `PlayerFrameOffsetTableA` (`$8FC0`), `PlayerFrameOffsetTableB` (`$9008`), `PlayerFrameOffsetTableC` (`$905C`) | Three 12/12/24-byte tables of packed frame data (`$8FC0`, `$9008`, `$905C`); the rest of the block is code. | read at run time (CDL Data) by code the tracer did not reach | | `$90F5-$9108` | 20 | - | Not a table: code (all 20 bytes are Code in the CDL, 2 Exec). | executed as code at `$90F5` | | `$9174-$92C9` | 342 | `PlayerSpriteTileTable` (`$9174`), `PlayerSpriteAttributeTable` (`$9188`) | `$9174-$9183` = 16 x 8-bit CHR tile indices in four groups of four (one group per sprite frame, four sprites each); `$9188-$9197` = 16 x 8-bit OAM attribute bytes in the same four groups. Both are handed to `WriteSpriteToOam`, which copies them into the OAM shadow at `$0200`. The rest of the block is code. | `lda $9174,y` at `$9126`, `lda $9175,y` at `$913B`, `lda $9176,y` at `$9152`, `lda $9177,y` at `$9166`; `lda $9188,y` at `$912B`, `lda $9189,y` at `$9140`, `lda $918A,y` at `$9157`, `lda $918B,y` at `$916B` | | `$92E9-$92F4` | 12 | `PlayerSpriteAngleTable` | 12 bytes of small angle/phase selectors (`$03 05 04 03 01 02 03 02 01 01 02 01`); the value is added with carry to the sprite's position accumulator. | `adc $92E9,x` at `$92E2` | | `$932F-$942F` | 257 | `PlayerRoutinePointerTable` (`$93CE`) | `$93CE-$93D1` = 2 x 16-bit little-endian pointers (`$93F9`, `$946F`); the rest of the block is code and a few bytes of packed sprite data. | `lda $932F,y` at `$931A` and `ora $932F,y` at `$9323` (the sprite table proper); `$93CE` is read at run time (CDL Data) | | `$946F-$A3CD` | 3935 | - | Not a table: code the tracer could not reach. The block contains `jsr $C606` and `jsr $C693` - calls into routines that *are* in the traced dump - and reads its own embedded data (`lda $94F0,y` at `$94A4`), so it is a body of code rather than a table. The CDL touched only 29 of the 3935 bytes, so almost none of it ran. | not referenced by any instruction in the traced dump | | `$A4A2-$A755` | 692 | `ScreenObjectPointerTableA` (`$A4A2`), `...B` (`$A4B8`), `...C` (`$A4D0`), `...D` (`$A4E4`), `...E` (`$A4EE`) | Five 16-bit little-endian pointer tables pointing into the same block: `$A4A2` (10 bytes = 5 pointers), `$A4B8` (24 bytes = 12 pointers), `$A4D0` (18 bytes = 9 pointers), `$A4E4` (6 bytes = 3 pointers) and `$A4EE` (14 bytes = 7 pointers). The rest of the block is code. | `lda $A4A2,y` at `$A444`, `lda $A4A3,y` at `$A449`, `lda $A4B8,y` at `$A44E`, `lda $A4B9,y` at `$A453`, `lda $A4D0,y` at `$A436`, `lda $A4D1,y` at `$A43B` | | `$A95A-$A96C` | 19 | - | Unreferenced data. The CDL marks every byte as untouched (no Code, no Data, no Exec) during the whole capture, and no instruction in the dump reads the range. | not referenced by any instruction | | `$ACDF-$ACE6` | 8 | `PlayerSpeedLimitTable` | 8 x 8-bit values in `$00`-`$1C` (0-28) - a per-slot speed or distance limit. | `lda $ACDF,y` at `$ACB4` | | `$ADCA-$ADE3` | 26 | `PlayerVelocityTable` | 26 x 8-bit signed fractional deltas, added with carry to the object's position accumulators (`Zp21`/`Zp22`). `$ADCA-$ADCB` = `$F8 08` (-8/256, +8/256), then `$ADCC`-`$ADCF`, `$ADD0`-`$ADD3`, `$ADD4`-`$ADD7`, `$ADD8`-`$ADDB` and `$ADE0`-`$ADE3` (an ascending run `$D9`-`$E4`). | `lda $ADCA,y` at `$AD70`, `adc $ADCC,y` at `$AD80`, `adc $ADD0,y` at `$AD96`, `adc $ADD4,y` at `$AD9C`, `lda $ADD8,y` at `$ADB9`, `lda $ADE0,y` at `$ADAE` | | `$AE41-$AE48` | 8 | - | Unreferenced data: eight bytes the CDL never touched and no instruction in the dump reads. | not referenced by any instruction | | `$AE84-$AE85` | 2 | `GoalMouthXPositionTable` | 2 x 8-bit X positions (`$40`, `$C0`) - the left and right goal mouths, stored straight into the object's X field. | `lda $AE84,y` at `$AE76` | | `$AF1A-$AF34` | 27 | `PlayerParameterTableA` (`$AF1A`), `PlayerParameterTableB` (`$AF2A`) | Two small tables of 8-bit parameters (`$AF1A` = `$30 40 60`, `$AF2A` = `$FF 08 18 10 00`); the rest of the block is code. | `lda $AF1A,y` at `$AEF3`, `lda $AF2A,y` at `$AEB5` and at `$AED7` | | `$B0BF-$B0C4` | 6 | `PlayerShotCountTable` | 6 x 8-bit counters (`$02 03 01 00 05 0A`) read as two interleaved 3-byte groups. | `lda $B0BF,y` at `$B0B1`, `lda $B0C1,y` at `$B0B8` | | `$B2C2-$B2CB` | 10 | - | Unreferenced data: the CDL never touched these ten bytes and no instruction in the dump reads them. | not referenced by any instruction | | `$B618-$B663` | 76 | `PlayerStatTable` (`$B618`) | A set of small 8-bit per-player statistics scattered through the block (`$B618` = `$00`, `$B620` = `$09`, `$B623` = `$08`, `$B628` = `$09`, `$B62A` = `$00 08`, `$B630` = `$02`, `$B632` = `$07 00`) plus three 16-bit pointers at `$B650`, `$B654`, `$B65E`. The rest of the block is code. | `lda $B618,y` at `$B60E` | | `$B6AF-$B73A` | 140 | - | Not a table: code (the CDL marks 71 of the 140 bytes as Code). | executed as code at `$B6AF` | | `$B967-$B9A6` | 64 | `RinkGridPositionTable` | 32 interleaved (Y, X) coordinate pairs - a 4 x 8 grid of rink positions. Y = `$1C`, `$34`, `$4B`, `$63`; X = `$1C`, `$30`, `$50`, `$70`, `$8F`, `$AF`, `$CF`, `$E3`. The pairs are read with a stride of 2, so the Y of pair *n* is at `$B967 + 2n` and its X at `$B968 + 2n`. | `lda $B967,y` at `$B903` -> `Zp8C,x` (Y), `lda $B968,y` at `$B90B` -> `Zp94,x` (X, mirrored with `eor #$FF` when `ZpBE` bit 0 is set) | | `$BA0F-$BA56` | 72 | `PlayerEventPointerTableC` (`$BA0F`) | `$BA11-$BA12` is a 16-bit little-endian pointer (`$BA2F`); the rest of the block is code. | read at run time (CDL Data) by code the tracer did not reach | | `$BB70-$BBB3` | 68 | `PlayerEventPointerTable` (`$BB70`), `PlayerEventPointerTableB` (`$BB76`) | `$BB70-$BB73` = 2 x 16-bit pointers (`$BB80`, `$BB9D`); `$BB76-$BB7B` = 3 x 16-bit pointers (`$BBAE` three times). The rest of the block is code. | `lda $BB70,y` at `$BB62`, `lda $BB71,y` at `$BB67` | | `$BC3F-$BC54` | 22 | `PlayerZoneBoundaryTable` | Pairs of small screen-coordinate bytes (`$BC3F` = `$00 08 00`, `$BC43` = `$04`, `$BC47` = `$08`, `$BC49` = `$08`, `$BC4D` = `$20`, `$BC51` = `$18`) delimiting the zone the object is in; the rest of the block is code. | `lda $BC3F,y` at `$BC1F` and at `$BC6C`, `lda $BC49,y` at `$BC23` and at `$BC70` | | `$BDC7-$BE26` | 96 | `ShotSpeedRankTable` (`$BDC7`), `ShotSpeedSearchTable` (`$BE07`) | A pair of ordered 8-bit tables used as a value-to-rank lookup. `$BE07-$BE25` runs *down* (`$2A` x7, `$29` x4, `$28` x2, `$27` x3, `$26` x2, `$25` x2, `$24` x2, `$23`, `$22` x2, `$20`, `$1F`, `$1E`, `$1D`) and is compared against the incoming value; `$BDC7-$BDD7` runs *up* (`$25`, then `$2B 2C 2D 2D`, `$2F 30 31 32`, `$DA`) and supplies the result. The rest of the block is code. | `cmp $BE07,x` at `$BD47` and `cmp $BE07,y` at `$BD5D`, `lda $BDC7,x` at `$BD50` and at `$BD66` | | `$C095-$C0F4` | 96 | `PlayerMovementDeltaTable` | 48 interleaved (Y, X) movement-delta pairs - the Y delta of pair *n* is at `$C095 + 2n` and its X delta at `$C096 + 2n`. The Y byte holds a 7-bit magnitude with the sign in bit 7 (`and #$7F`, sign preserved through `php`/`plp`); the X byte is a signed 8-bit delta. The index is `($0357,x * 16) \| (Zp3C,x & $0F)`, so the table covers three sprite sets of 16 directions. | `lda $C095,y` at `$C03F` (Y delta) and `lda $C096,y` at `$C04A` (X delta), both feeding the object's 16-bit position accumulators `Zp1F`/`Zp21` | | `$C183-$C605` | 1155 | `PlayerSpriteFlagTable` (`$C183`), `PlayerSpriteIndexTable` (`$C1E3`), `PlayerFacingOffsetTableB` (`$C263`), `PlayerSpriteIndexTableB` (`$C2A3`), `PlayerFacingOffsetTableC` (`$C2E3`), `PlayerFacingOffsetTableD` (`$C323`), `PlayerSpriteFlagTableB` (`$C363`), `PlayerSpriteAttributePointerTable` (`$C3C3`), `PlayerSpriteOffsetPointerTable` (`$C3D5`), `PlayerSpriteTilePointerTable` (`$C3FB`), `PlayerSpriteAttributePointerTableB` (`$C473`), `PlayerSpritePaletteTable` (`$C47B`), `...B` (`$C4A8`), `...C` (`$C4B5`), `PlayerSpriteTileSetTable` (`$C4FB`) (+ B..G at `$C53B`, `$C54B`, `$C59B`, `$C5D6`, `$C5E5`, `$C5F2`) | The sprite-drawing data block. 8-byte flag tables (`$C183`, `$C363`); 16-entry index tables (`$C1E3`, `$C2A3`); 16-byte facing-offset profiles (`$C263` = `$04 10 0B 0C ...`, `$C2E3` = `$14 15 16 17 ...`, `$C323` = `$28 29 2A 2B ...`); four 16-bit pointer tables that select the offset, tile and attribute lists (`$C3C3`, `$C3D5`, `$C3FB`, `$C473`); NES palette bytes (`$C47B`, `$C4A8`, `$C4B5`); and CHR tile-index lists for the player sprites (`$C4FB` onwards, e.g. `$C4FB` = `$35 35 36 37 26 27 28 29 04 05 06 07 ...`). The rest of the block is code. | directly: `lda $C183,x` at `$C116`, `lda $C1E3,y` at `$C104`, `lda $C2A3,y` at `$C0F5`, `lda $C363,x` at `$C130`, `lda $C3C3,x` at `$C123`, `lda $C3C4,x` at `$C128`, `lda $C3D5,x` at `$C109`, `lda $C3D6,x` at `$C10E`, `lda $C3FB,x` at `$C0FA`, `lda $C3FC,x` at `$C0FF`, `lda $C473,x` at `$C13D`, `lda $C474,x` at `$C142`. Indirectly: the facing-offset profiles, the palettes and the tile sets are reached through those four pointer tables | | `$C767-$C986` | 544 | `PlayerStatLimitTable` (`$C767`), `PlayerRoutinePointerTableB` (`$C787`), `PlayerRoutinePointerTableC` ($C887) | `$C767` = 8 x 8-bit limits compared against the object's value; `$C787` and `$C887` are two 16 x 16-bit little-endian pointer tables into the `$C47B`-`$C5FF` sprite data. The rest of the block is code. | `cmp $C767,x` at `$C735`, `cmp $C767,y` at `$C750`, `lda $C787,x` at `$C73F`, `lda $C788,x` at `$C744`, `lda $C887,y` at `$C75A`, `lda $C888,y` at `$C75F` | | `$CA88-$CB9F` | 280 | `ZoneBoundaryLowTable` (`$CA88`), `ZoneBoundaryHighTable` (`$CA89`), `ZoneRoutinePointerTable` (`$CAC0`), `ZoneRoutinePointerTableB` (`$CB30`) | `$CA88`/`$CA89` are two 8-bit limit tables one byte apart (the low and high bound of the zone a position may occupy); the code compares a position against the low table with `cmp $CA88,y` and against the high table with `cmp $CA89,y`. `$CAC0` and `$CB30` are two 16-bit pointer tables 112 bytes apart. The rest of the block is code. | `cmp $CA88,y` at `$CA3C`, `cmp $CA89,y` at `$CA41`, `lda $CAC0,y` at `$CA76`, `lda $CB30,y` at `$CA81` | | `$CBCB-$CC09` | 63 | `ZoneCheckTable` (`$CBCB`), `ZonePointerTable` (`$CBD2`), `ZonePointerTableB` (`$CBEE`) | `$CBCB` = 8-bit zone-check values; `$CBD2` and `$CBEE` are two 16-bit pointer tables. The rest of the block is code. | `cmp $CBCB,y` at `$CBAC`, `lda $CBD2,y` at `$CBBF`, `lda $CBEE,y` at `$CBC4` | | `$CC42-$CC65` | 36 | `PlayerMaskTable` (`$CC42`), `PlayerMaskTableB` (`$CC46`) | Two 8-bit tables four bytes apart, OR-ed into the object's attribute byte. The rest of the block is code. | `lda $CC42,x` at `$CC37`, `ora $CC46,x` at `$CC1B` and at `$CC29` | | `$CD22-$CDF1` | 208 | `PlayerAttributeTableB` (`$CD22`), `...C` (`$CD32`), `...D` (`$CD42`), `...E` (`$CD52`), `...F` (`$CD5A`), `...G` (`$CD62`) | Six 16-byte 8-bit tables at a stride of 16 - one per player slot - holding the per-slot attribute bytes. The rest of the block is code. | `lda $CD32,x` at `$CC6E`, `lda $CD52,x` at `$CC77`, `lda $CD62,x` at `$CC7C`, `lda $CD42,x` at `$CC81`, `lda $CD3A,x` at `$CC8C`, `lda $CD5A,x` at `$CC95` | | `$CE51-$CE80` | 48 | `PlayerLimitColumn0Table` (`$CE51`), `PlayerLimitColumn1Table` (`$CE52`), `PlayerLimitColumn2Table` (`$CE53`), `PlayerLimitColumn3Table` (`$CE54`) | Four 8-bit tables one byte apart - four columns of the same limit matrix, read with the same index. The rest of the block is code. | `lda $CE51,x` at `$CE87`, `lda $CE52,x` at `$CE8C`, `lda $CE53,x` at `$CE9D`, `lda $CE54,x` at `$CEA2` | | `$D02B-$D06D` | 67 | - | Not referenced by any instruction in the traced dump. The CDL marks `$D02B-$D046` as read as data during the capture, so a reader exists in code the tracer did not reach; the bytes are 8-bit CHR tile indices (`$04 9C 9C 9C 9C 9C 9C 9C 9C 9C 9C 00 07 C7 CF C1 CC DC 00 00 05 D0 C5 D2 C9 CF C4 00`). | not referenced by any instruction in the traced dump (CDL Data at `$D02B-$D046`) | | `$D143-$D153` | 17 | - | Not referenced by any instruction in the traced dump and never touched by the CDL. | not referenced by any instruction | | `$D1A6-$D1AD` | 8 | `PlayerPeriodTable` | Two 4-byte interleaved tables of timing constants: `$D1A6` = `$00 20 A0 23` and `$D1A7` = `$20 A0 23 00` (the same four values one byte apart), giving period/phase pairs per slot. | `lda $D1A6,y` at `$D166`, `lda $D1A7,y` at `$D16B` | | `$D225-$D31C` | 248 | `ZoneParameterTable` (`$D22D`), `ZoneParameterTableB` ($D232) | `$D22D-$D230` = `$10 20 18 38 00 10` and `$D232` = `$10`: small numeric parameters for the state routine at `$D331-$D37B`, which lies inside the same block. The rest of the block is code the tracer did not reach. | `lda $D22D,y` at `$D331`, `lda $D22E,y` at `$D337`, `lda $D22F,y` at `$D347`, `lda $D230,y` at `$D372`, `cmp $D232,y` at `$D37B` | | `$D3BE-$D3C1` | 4 | `PlayerEventPointerTableD` | 2 x 16-bit little-endian pointers (`$8685`, `$8A89`). | `lda $D3BE,y` at `$D385` | | `$D407-$D634` | 558 | `RinkZonePatternSelectTableA` (`$D40A`), `...B` (`$D410`), `...C` (`$D411`), `...D` (`$D412`), `...E` (`$D413`), `RinkZoneParameterTableA` (`$D467`), `...B` (`$D4DF`), `...C` (`$D50F`), `...D` (`$D53F`), `...E` (`$D54B`), `...F` (`$D557`), `RinkZoneTileSelectTable` (`$D56F`), `RinkZoneTileTable` (`$D587`), `RinkZoneAttributeTable` (`$D588`), `RinkZoneCoordinateTable` (`$D5B7`), `RinkZoneParameterTableG` (`$D5E7`), `...H` (`$D5FF`), `RinkZoneLimitTable` (`$D62F`) | The rink-zone screen parameters. The five `RinkZonePatternSelectTable` bytes are OR-ed into the PPUCTRL-like value the zone is drawn with; `$D587`/`$D588` are the zone's tile and attribute bytes; `$D5B7-$D5BE` = `$58 20 60 38 60 40 40 70` - four (X, Y) coordinate pairs; the `RinkZoneParameterTable` bytes are added to and compared against the object's position accumulators. The rest of the block is code. | `ora $D40A,y` at `$D70A`, `ora $D410,y` at `$D8BD`, `ora $D411,y` at `$D659`, `ora $D412,y` at `$D689`, `ora $D413,y` at `$D751`, `lda $D467,y` at `$D6BE`, `lda $D468,y` at `$D6C3`, `lda $D4DF,y` at `$D712`, `lda $D4E0,y` at `$D718`, `lda $D50F,y` at `$D720`, `lda $D510,y` at `$D726`, `sbc $D53F,y` at `$D796`, `adc $D540,y` at `$D7A2`, `sbc $D54B,y` at `$D7C4`, `adc $D54C,y` at `$D7D0`, `lda $D557,y` at `$D85F`, `lda $D56F,y` at `$D8C1`, `lda $D587,y` at `$D66C`, `lda $D588,y` at `$D671`, `lda $D5B7,y` at `$D678`, `lda $D5B8,y` at `$D67D`, `lda $D5E7,y` at `$D68D`, `lda $D5FF,y` at `$D755`, `cmp $D62F,y` at `$D827` and `$D843` | | `$D8D9-$ECFF` | 5159 | `PlayerEventIndexTable` ($D972), `PlayerFacingAngleTable` ($D986), `PlayerEventVectorTable` ($D99C), `...B` ($D9DF), `PlayerEventParameterTable` ($D9F7), `...C` ($D9FD), `ZoneAttributeTableA` ($DC92), `...B` ($DC97), `ZoneOffsetTable` ($DC9C), `...B` ($DCBC), `ZoneBoundaryTableA` ($DCDC), `...B` ($DCE2), `...C` ($DCE8), `...D` ($DCEE), `CompressedScreenData` ($DCEC), `PlayerDirectionIndexTable` ($E7F9), `RinkZoneCoordinateTableB` ($EC80) | The game's main data region. `$D972-$D985` = 8-bit event indices (0-2); `$D986-$D995` = 12 heading bytes (`$40 30 50 50 40 60 40 38 48 60 50 70`); `$D99C-$D9E6` = 75 bytes of packed event vectors in four-byte records; `$D9DF` = the per-object event vectors; `$D9F7` = `$27 25`; `$D9FD-$DA7C` = 128 bytes = 32 records of two 16-bit values; `$DC92`/`$DC97` = single zone-attribute bytes; `$DC9C`/`$DCBC` = zone offsets; `$DCDC-$DCEE` = the zone boundary values the position is compared against; `$E7F9` = the direction index that selects a `PlayerDirectionAngleTable` group. **`$DCEC-$E7BE` (2771 bytes) is the RLE-compressed nametable data**: a record is a count byte (bit 7 clear = that many literal tile bytes; bit 7 set = a run of `count & $7F` copies of the following byte) and the stream ends on a `$00` count. `ScreenLayoutPointerTable` selects four of these streams (`$DCF4`, `$DE0D`, `$DFA0`, `$E1D5`). | `lda $D972,y` at `$88F5` (copied into `$0358`), `lda $D986,y` at `$8F8B`, `lda $D99C,y` at `$AE6B` and at `$B36F`, `lda $D9DF,y` at `$D0A5` and `$D0B6`, `lda $D9F7,x` at `$B29A`, `lda $DC92,y` at `$AB2C`/`$AB3F`, `lda $DC97,y` at `$AB99`/`$ABAD`, `lda $DC9C,y` at `$C99D`, `lda $DCBC,y` at `$C9AE`, `cmp $DCDC,y` at `$BE7B`, `cmp $DCE2,y` at `$BE82`, `cmp $DCE8,y` at `$BE8E`, `cmp $DCEE,y` at `$BE95`, `lda $E7F9,y` at `$8843`; the compressed screens are read through `lda (Zp0A),y` at `$8256`/`$8262`/`$8271` | | `$ED62-$EEB3` | 338 | `PlayerSpritePageTable` ($ED62), `PlayerSpriteAttributeColumnTable` ($ED65), `PlayerSpriteTileColumnTable` ($ED68) | Three 8-bit columns one byte apart ($ED62 = `$B8 CA DC 00 01 03 06 46`, $ED65 = the attribute bytes, $ED68 = the tile bytes) describing the eight sprite pages. The rest of the block is code. | `lda $ED62,x` at `$ED32`, `lda $ED65,x` at `$ED37`, `lda $ED68,y` at `$ED3C` | | `$EEED-$F8FF` | 2579 | `SoundEngineData` ($EEED), `SoundInstrumentTableA`..`K` ($F000, $F008, $F010, $F017-$F01E), `SoundNoteTable` ($F870), `SoundNoteTableB` ($F8BE) | The sound engine's data. `$F000`-`$F01E` are the instrument definitions: each entry holds a pulse duty/volume byte, a sweep byte and a timer byte that are written straight to `APUPULSE1CTRL`, `APUPULSE1TIMERLO` and friends. `$F870-$F8B5` and `$F8BE-$F8FD` are note tables - the bytes are note indices (`$8B`, `$8C`, ...), and the region from `$F0A5` on is a stream of note/period data. The rest of the block is the engine's code and note streams. | `lda $F000,y` at `$FD1C`, `lda $F008,y` at `$FD22`, `lda $F010,y` at `$FD2B`, `lda $F017,y` at `$FD54`, `lda $F018,y` at `$FD58`, `lda $F019,y` at `$FD5E`, `lda $F01A,y` at `$FD63`, `lda $F01B,y` at `$FD68`, `lda $F01C,y` at `$FD6E`, `lda $F01D,y` at `$FD74`, `lda $F01E,y` at `$FD79`; `lda $F870,y` at `$FFA7`, `lda $F871,y` at `$FFAC`, `lda $F8BE,y` at `$FF2A`, `lda $F89F,y` at `$FF2F`, `lda $F8B6,y` at `$FF37`, `lda $F888,y` at `$FF3C` | | `$F994-$F9D2` | 63 | `SoundEffectTableC` ($F9A3), `...D` ($F9B3), `...E` ($F9B9), `...F` ($F9BB) | Pulse-channel sound-effect parameters: period, sweep and duty bytes for the effects, plus a 21-byte envelope/descending run at `$F9BE`. | `ldx $F9A3,y` at `$FB24`, `lda $F9B3,y` at `$FAE9`, `lda $F9B9,y` at `$FA3C`, `lda $F9BB,y` at `$F9F3` | | `$FB34-$FB3F` | 12 | `SoundEffectTableG` (`$FB39`) | 12 bytes of sound-effect selectors (`$47 49 42 4A 43 4B ...`); `$FB34-$FB38` is code. | `lda $FB39,y` at `$FB5C` | | `$FB7C-$FBD7` | 92 | `NoisePeriodTable` ($FB7C), `...B` ($FB9C), `...C` ($FBBC), `...D` ($FBC3), `...E` ($FBCA), `...F` ($FBD1) | The noise-channel tables. `$FB7C` is the noise period index written to `APUNOISEPERIOD`; `$FB9C` is the noise control byte; `$FBBC`/`$FBC3`, `$FBCA`/`$FBD1` are four parallel 6-byte control tables. | `lda $FB7C,y` at `$FC2F`, `lda $FB9C,y` at `$FC27`, `lda $FBBC,y` at `$FBE6`, `ldx $FBC3,y` at `$FBE3`, `lda $FBCA,y` at `$FBF9`, `ldx $FBD1,y` at `$FBF6` | | `$FC79-$FCA0` | 40 | `SoundEffectTableH` | 40 bytes; `$FC87-$FC9B` is a descending run of 21 8-bit values (`$BC $CC $BB $AA $99 $88 $77 $77 $89 $AB ... $43`) - an effect envelope. | `lda $FC79,y` at `$FCC7` | | `$FF04-$FF09` | 6 | - | Unreferenced data: the CDL never touched these six bytes and no instruction in the dump reads them. | not referenced by any instruction | | `$FF40-$FF6F` | 48 | `PlayerDirectionMaskTable` | 48 bytes in six 8-byte groups of packed mask bits (`$04 08 10 20`, `$04 18 0C 30`, `$05 0A 14 28`, `$04 1E 0F 3C`, `$06 0C 18 30`, `$60 24 12 48`, ...). The group is picked by `$0306` and the entry by the low three bits of the index. | `lda $FF40,y` at `$FF11` | | `$FFE1-$FFFF` | 31 | `TitleString` ($FFE1) | `$FFE1-$FFF0` = the ASCII text `2/23/ICE HOCKEY}` - the date and the title, terminated by `}`. The string actually starts one byte earlier, at `$FFE0`, which holds `1` and is the last byte of the preceding code block. `$FFF1-$FFF9` = `$CA $35 $3D $20 $00 $01 $09 $01 $63`, and `$FFFA-$FFFF` are the NMI/RESET/IRQ vectors. | not referenced by any instruction (the vectors are fetched by the CPU, not read by an instruction) | ### Notes on the names - The tables fall into five groups, and the names follow that grouping: the rink/sprite drawing data (`PlayerSprite*`, `PlayerStart*`, `RinkGrid*`), the game-state dispatch tables (`GameStateJumpTable`, `SpriteJumpTablePointerTable`), the rink-zone data (`Zone*`, `RinkZone*`), the sound engine data (`Sound*`, `Noise*`), and the compressed nametable data (`CompressedScreenData`). - Where two tables have the same shape and the ROM gives no way to tell them apart, they carry a letter suffix. `PlayerRoleRemapTableA`-`D` are four role-remap tables of the same shape; `ScreenObjectPointerTableA`-`E` are five pointer tables of the same shape; `SoundEffectTableC`-`H` and `NoisePeriodTable`-`F` are sound tables of the same shape; `RinkZonePatternSelectTableA`-`E` and `RinkZoneParameterTableA`-`H` are rink-zone tables of the same shape. - Two tables are byte-interleaved rather than adjacent, and their names say so: `PlayerStartYPositionTable`/`PlayerStartXPositionTable` are adjacent 8-byte tables, but `RinkGridPositionTable` is one table of 32 interleaved (Y, X) pairs, `PlayerMovementDeltaTable` is one table of 48 interleaved (Y, X) deltas, and `PlayerDirectionAngleTableA`-`D` are four groups at a stride of 6. - Blocks the CDL marks as Code are recorded above as code the tracer could not reach, not as data; they are left unnamed in `prg.s` on purpose. A block that decodes as a long run of instructions is code, so the byte values were not given a table name. - A few named sub-tables sit inside blocks whose *only* reader lies outside the traced code (`PlayerEventIndexTable`, `PlayerFacingAngleTable`, `PlayerEventVectorTableC`, `RinkZoneCoordinateTableB`, the `PlayerEventRoutinePointerTable` at `$8B12` and the `PlayerObjectRoutinePointerTable` at `$8CFB`). The Mesen CDL marks those bytes as read as data during the capture, which is why they are named here even though no instruction in `prg.s` names them. ## Address-derived entry points (residue) `PROMPTS.md` asks every `jsr`/`jmp` entry point to carry a semantic name and accepts the address-derived `SubCXXXX` form **only where the dump does not reveal a better one**. Of the 219 address-derived entry points this project started with, **140 now carry a semantic name** (for example `ClearGameMemory` at `$8111`, `UploadPaletteToPpu` at `$8194`, `DecompressScreenData` at `$8254`, `PollControllerButtons` at `$82F9`, `DispatchThroughPointerTable` at `$8359`, `WriteSpriteToOam` at `$8AD8`, `PlaySoundEffect` at `$D0D8` and `UpdateSoundEngine` at `$F900`). **79 entry points remain address-derived.** They fall into four groups, and in every case the reason is the same - the dump does not settle what the routine is *for*: | Group | Count | Addresses | Why the dump does not settle it | |-------|-------|-----------|--------------------------------| | Player control, movement and contact | 59 | `$9314`, `$A933`-`$A959`, `$AB0F`-`$AB4F`, `$ABCD`-`$AC79`, `$AD21`, `$AE08`-`$AE86`, `$AEB8`-`$AFE5`, `$B008`-`$B0C5`, `$B21A`-`$B481`, `$BB14`-`$BCF6`, `$BDC6`, `$BEB6`, `$BF1A`-`$BF8`, `$C027`, `$C6D2`-`$C714`, `$CE0E`-`$CE2E` | These are the movement, contact and role-resolution steps of the player/puck simulation. Each one is a few instructions of arithmetic on the per-player arrays (the `Zp8C`/`Zp94`/`ZpA4`/`ZpAC` position bytes, the `Zp3C`/`Zp44` facing bytes, the `$0442`/`$0444` event slots). Which side of a contact test each helper sits on, and what the `ZpB6`/`ZpB7`/`ZpBB` byte pair means, is not recoverable from the instructions: the same bytes are read both indexed and absolute inside one routine, and no table in the ROM labels them. | | Rink-zone drawing and scoring | 11 | `$BAA1`, `$CF1F`, `$CF2A`, `$D3F0`, `$D406`, `$D635`, `$D69E`, `$D702`, `$D773`, `$D7FE`, `$D871` | These read the rink-zone parameter tables (`RinkZone*`) and the `Zone*` tables whose layout the data-table inventory records as "read as 8-bit parameter bytes whose meaning is not established". The zone coordinates are 8.8 fixed-point pairs, so the code that consumes them computes positions that no label can describe without a runtime trace. | | Player-event / role resolution | 4 | `$B153`, `$B159`, `$B15B`, `$B15F` | These set the rink zone's tile and attribute bytes from the `ZoneAttribute*` tables. The tables are interpreted as one byte per zone, but the ROM never says which zone each byte belongs to, so the routine can only be described as "apply the zone's attribute", not as "redraw the blue line". | | Sound-engine note sequencer | 5 | `$FD34`, `$FD37`, `$FD9B`, `$FDFF`, `$FE7B` | The sound engine is a note sequencer: `$031B` is a note index, `$0306`/`$0307` are stream pointers and the `SoundNoteTable`/`SoundEffectTable*` bytes are note indices whose pitch meaning the ROM does not state. These routines advance a pointer and write an APU register; which note they play is decided by the queue the caller built. | These names are left as they are rather than guessed: a plausible-sounding name that the ROM does not support would be worse than the honest `SubCXXXX` form that `PROMPTS.md` explicitly allows. ## Open work - **Pass 1** - run `nesdis.py` over `prg.bin` to produce the code/data split. The tracer reads the vectors from the last six bytes of the PRG image, so the split seeds correctly. - **Pass 1** must emit the canonical audit comment `; CPU:$C123 PRG:0123 AD 21 03` at column 35 for every instruction. - **Pass 1** - re-trace with the Mesen jump-target seeds (`_tools/cdl_seeds.py icehockey_gameplay.cdl`) so that the blocks this project's inventory records as "code the tracer could not reach" are decoded as code instead of sitting in `.byte` rows. - **Pass 2** - replace the address-derived `Label_XXXX` names and the raw `$XXXX` call operands. - **Pass 3** - semantic zero-page aliases (applied only at the sites where their role is proven) and semantic names for the code labels. The data tables are already named; see the inventory above. - **Promotion** to `NROM//` once Pass 3 is done, as `git mv`. ## Files | File | Purpose | |------|---------| | `prg.s` | Pass 0 PRG include (replaced by the Pass 1 disassembly) | | `header.s` | iNES header include | | `chr.s` | CHR include | | `prg.bin` | 32768 bytes (32 KB) PRG image | | `chr.bin` | 8192 bytes (8 KB) CHR image | | `rom.nes` | byte-identical copy of the original ROM | | `linker.cfg` | HEADER / PRG / CHR memory areas | | `symbols.inc` | NES hardware register names | | `locals.inc` | zero-page aliases (empty at Pass 0) | | `Makefile` | build and byte-identity check |