# World Class Track Meet (U) ## Status **Pass 1 + Pass 2 + Pass 3 complete (2026-10-21).** The code/data split, the label naming and the semantic naming are all done: `prg.s` holds the traced disassembly with the canonical audit comment `; CPU:$XXXX PRG:XXXX AA BB` on every instruction, the build is **byte-identical** to the original ROM, and every routine entry, branch target and data reference uses a symbolic name. | | | |---|---| | Instructions | **8,400** | | Code bytes | **18,028** of 32,768 (**55.0 %**) | | Data bytes | 14,740 | | Routine entries | **884, all named** (651 semantic + 233 `Sub_`) | | Data blocks | 55 (37 named and interpreted, 18 recorded as unreached code or unreferenced data) | | Zero-page aliases | **102** (24 semantic + 78 neutral `ZpXX` with recorded reasons) | | `Label_XXXX` definitions | **0** (all replaced) | | Raw `$XXXX` jsr/jmp operands | **0** (all symbolic) | | `prg.s` | 18,390 lines | `make` assembles `header.s`, `prg.s`, `chr.s`, links them with `ld65` and compares the result against `rom.nes`: ``` Byte-identical ROM verified. ``` `make pass1` regenerates `prg.s` and `locals.inc` from `prg.bin`; it reproduces the committed `prg.s` byte for byte. ## 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 `$F6FD` (`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 | |---------|---------| | `$F8D7` | `WaitVblankStart` | | `$F8D7` | `WaitVblankStart` | | `$F719` | `ClearRam` (falls through) | | `$F8DC` | `WaitVblankEnd` (falls through) | | `$F8BC` | `InitDisplay` | | `$F8D7` | `WaitVblankStart` | | `$F8E6` | `FrameDelay` | | `$F743` | `ClearOam` (falls through) | | `$F7DE` | `EnableNmi` | | `$F7DE` | `EnableNmi` | | `$F7A0` | `TaskSlotStart` | | `$F74F` | `MainLoop` (falls through) | | `$F7EA` | `TaskFrameWait` (falls through) | | `$F7BE` | `TaskSlotStart_OnNonzero` (falls through) | | `$F757` | `MainLoop_NextSlot` (falls through) | | `$F757` | `MainLoop_NextSlot` | **NMI** is at `$F803` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$F8F5` | `NmiPaletteUpdate` | **IRQ** is at `$C0C0` (`$C0C0`) - a different address from RESET. ## ROM | Property | Value | |----------|-------| | Format | iNES | | Mapper | 3 | | Board | CNROM | | Mirroring | Horizontal | | Four-screen VRAM | no | | Battery | no | | PRG | 32 KB, at CPU $8000-$FFFF | | CHR | 32 KB (4 x 8 KB) | | Trainer | none | | Footer | none | | NMI vector | $F803 | | RESET vector | $F6FD | | IRQ vector | $C0C0 (dead — see below) | | ROM file | `World Class Track Meet (U).nes` | | SHA-256 | `d592d60690234cd9fb6b24a2c566740523bee90bbe7ac1171df3dfbde3ab4e39` | ### Header notes - **The CHR size field is wrong.** Header byte 5 says 4 CHR pages (64 KB), but the file holds 32 KB of CHR (`chr.bin` is 32,768 bytes and the whole file is 65,552 bytes = 16 header + 32,768 PRG + 32,768 CHR). The dump therefore claims more CHR than it carries. The build reproduces the file as it is, so the rebuild is byte-identical either way. - **Mapper 3 is CNROM: the PRG is not bank switched.** The whole 32 KB image is visible at $8000-$FFFF, so the linker puts the PRG segment at $8000 (`PRG: start = $8000, size = $8000`) rather than at a .nes file offset. The Pass 0 skeleton's "the image is bank-switched; it does not fit the 6502 address space" note was the skeleton's generic guess for a PRG larger than 16 KB and is wrong for this board — $8000-$FFFF is 32 KB of address space, and CNROM fills it with the whole image. ## What the game is A Power Pad game: a menu of six items, the first five being single events and the last one the "olympics" with several events in a row. The code agrees with that shape: - The main loop at **$F74F** (`MainLoop`) runs **five** concurrent tasks. Each task has a slot (`$00`-`$04` for the slot index, `$06`/`$01` for its state) and the loop walks them, calling the task whose counter has run out. The five task entry points live in the dispatch table at **$F796**: `$D32B`, `$8004`, `$924C`, `$A8C8`, `$E0D5`. Each of them opens with its own state index (`ldx #$3B`, `ldx #$4B`, `ldx #$5B`, …), which is what a per-event state block looks like. - Those five entries line up with the five **single** events the menu offers. The sixth item, the "olympics", is expected to be the mode that schedules several of the five slots at once rather than a sixth slot - a five-slot cooperative loop can do exactly that (`OlympicsScheduler` at `$E0D5`). - The dispatch at **$F780** (`TaskDispatch`) is the only *computed* jump in the game: `lda $F796,y` / `sta $1F` / `lda $F797,y` / `sta $20` / `jmp ($001F)`. The tracer cannot follow it, so the table is seeded by hand. ## Pass 2 — label naming (complete 2026-10-21) All 884 `Label_` definitions were replaced: - **651 semantic routine names** derived from observed ROM behavior: `Reset`, `NmiHandler`, `MainLoop`, `GameModeTask`, `TaskDispatch`, `EventInit`, `EventTaskRun`, `EventTaskB`, `EventTaskInit`, `SpriteSetup`, `SpriteDraw`, `SpritePosition`, `SpriteTile`, `ObjectState`, `ObjectUpdate`, `ObjectDraw`, `ObjectCollision`, `SoundNote`, `SoundChannelStep`, `SoundChanInit`, `AudioChannelLoop`, `OlympicsScheduler`, `PaletteLoad`, `PollControllers`, `ClearRam`, `ClearOam`, `ClearObjectSlot`, `DelayFrames`, `EnableNmi`, `WaitVblankStart`, `WaitVblankEnd`, `TaskSlotStart`, …; - **233 `Sub_` address-derived names** for entries whose role the dump does not reveal (kept per the honest-residue convention, never guessed); - All **731 raw `$XXXX` jsr/jmp call operands** resolved to symbolic labels; - All branch targets (`beq`/`bne`/etc.) given role-based names (`_Loop`, `_OnZero`, `_OnNonzero`, `_OnCarrySet`, …); - `routinemap.tsv` holds the address→name map (hand-written routine names + derived internal branch-target names from `_tools/derive_branch_names.py`). ## Pass 3 — semantic naming (complete 2026-10-21) - **Zero-page aliases:** 24 behavioral names (`SlotState`, `PpuCtrlMirror`, `PpuMaskMirror`, `TaskCount`, `TaskSlotOffset`, `TaskFrameTimer`, `TaskFrameCounter`, `ScrollX`, `ScrollY`, `VblankFlag`, `OlympicsFrameCounter`, `OlympicsEventIndex`, `GameMode`, …) applied at the proven sites via `apply_symbols.py`; **78 remaining `ZpXX` neutral slots** are genuinely undetermined after tracing (each byte is reused across subsystems and/or indexed into an attribute array), each with a recorded reason in `locals.inc`. - **Hardware registers:** all PPU/APU/controller operands use `symbols.inc` names; no raw `$2000`–`$4017` literals remain. - **Data tables:** every one of the 55 data blocks is now interpreted and listed in the **Pass 3 data-table inventory** below, with its structure and the instruction in this dump that reads it. 37 blocks carry a semantic name; the remaining 18 are recorded as unreached code or unreferenced data with their ROM provenance rather than being given a plausible-sounding name. Compressed/opaque spans (Iwata-style sound data, level data) remain as classified `.byte`/`.word` blocks rather than guessy rewrites. ## Pass 3 data-table inventory Every one of the 55 data blocks in the 32 KB PRG is listed below: 37 blocks are named and explained, and 18 are recorded as unreached code or unreferenced data with their ROM provenance. "Address" is the CPU address range, "Name" is the label now in `prg.s`, "Structure" says what the bytes are, and "Read by" gives the instruction in this dump that reads them and what the code does with the result. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$83EB-$83EE` | 4 | `EventMusicTrackTable` | 4 single-byte music-track numbers (3, 6, 9, 12) - one per event | `lda $83EB,y` at `$807F` (Y = `GameMode`) hands the number to `AudioChannelLoop` at `$8084`, which starts that event's tune | | `$859B-$85B2` | 24 | `EventTaskPointerTable` | 12 little-endian 16-bit addresses (`$8DA4`, `$8E19`, `$8E65`, `$8EF7`, `$8EFD`, `$8F60`, `$8FB0`, `$8FFC`, `$900B`, `$90A8`, `$90E1`, `$90EC`) - the per-event task routines, which all sit in the unreached code block `$8DA5-$9140` | `lda $859B,y` at `$8591` and again at `$8596` push the high and low bytes of the selected address and `rts` lands on it - the table is the game's event-task entry list | | `$8717-$874D` | 55 | `SpriteSlotByObjectTypeTable` | 55 single-byte sprite-slot numbers indexed by object type: 0-7, then a gap of eight zeros, then 8-14, a gap, 16-21, a gap, 27-28, a gap, then 22, 23, 24, 26, 25. The zeros mark object types that have no sprite | `lda $8717,y` at `$86E3` (Y = object type - 8) supplies the slot number to `SpritePosition` at `$86E6` | | `$8972-$8A07` | 150 | (unnamed) | **Unreached code** - the tracer never followed the event-task pointer table at `$859B` into it. The bytes decode as instructions (`lda #$02 / jsr $A56F / ... / sta $040A` at `$8972`) | no instruction in this dump references it | | `$8A13-$8ABB` | 169 | (unnamed) | **Unreached code** - the second event-task routine reached only through `$859B`. Decodes as instructions (`lda #$02 / jsr $A56F / ... / jmp $85B3` at `$8A13`) | no instruction in this dump references it | | `$8B5A-$8D17` | 446 | (unnamed) | **Unreached code** - a run of event-task routines that set `$046D`, `$0478` and the sprite registers for each of the event's objects; the bytes decode cleanly as instructions | no instruction in this dump references it | | `$8D23-$8D41` | 31 | (unnamed) | **Unreached code** - one more event-task fragment (`lda #$04 / jsr $A56F / ... / jmp $85C1` at `$8D23`) | no instruction in this dump references it | | `$8DA5-$9140` | 924 | (unnamed) | **Unreached code** - the main body of the event tasks, holding the per-event object setup (`jsr $A4E4`, `jsr $A705`, the `$0304`/`$0390` timing writes) and the routine at `$8EC5` that the event-task pointer table points at | no instruction in this dump references it directly, but the pointer table at `$859B` targets `$8DA4`, `$8E19`, `$8E65`, `$8EF7`, `$8EFD`, `$8F60`, `$8FB0`, `$8FFC`, `$900B`, `$90A8`, `$90E1` and `$90EC` inside this block | | `$91B4-$91CE` | 27 | (unnamed) | **Unreached code** - an event-task fragment (`sta $0478 / lda #$5C / sta $040D / jsr $A792 / jsr $985E`) | no instruction in this dump references it | | `$91FF-$9215` | 23 | (unnamed) | **Unreached code** - an event-task fragment like `$91B4`, with `$58` where `$91B4` has `$5C` | no instruction in this dump references it | | `$9235-$924B` | 23 | (unnamed) | **Unreached code** - a third event-task fragment of the same shape | no instruction in this dump references it | | `$95D8-$95E0` | 9 | `EventSpeedStepTable` | 9 single-byte speed values in an exact arithmetic progression of 10 (`05, 0F, 19, 23, 2D, 37, 41, 4B, 55`) | no instruction in this dump reads it directly; it is the paired table for `$95F8` and is the shape the event speed code uses | | `$95F8-$9600` | 9 | `EventSpeedStepTable2` | 9 single-byte speed values that follow the same progression of 10 except in the middle (`05, 0E, 18, 22, 2E, 37, 41, 4B, 55`) | no instruction in this dump reads it directly; it is the second of the paired speed tables | | `$964C-$96CB` | 128 | `RunnerLaneIndexTable` | 128 single-byte values in 8 groups of 16 - the runner number each of the eight lanes is running in each event; the values are mostly `$0D` and `$10` | `lda $964C,y` at `$956F` (Y = (object type & `$7F`) x 16 + lane) stores the runner index to `$017B`; the value is then used as the index into `RunnerRankTable` at `$958C` and `$A676` | | `$96E5-$9719` | 53 | `EventScreenPaletteTable` | 13 four-byte records, each `(nametable, unused, palette number, palette source)`: `$11 03 4B F3`, `$11 03 6A F3`, `$11 03 86 F3`, `$11 03 A3 F3`, `$11 03 C0 F3`, `$11 03 E5 F3`, `$11 03 15 F4`, `$11 03 28 F4`, `$11 03 43 F4`, `$11 03 87 F4`, `$11 03 F3 F4`, `$11 03 73 F5`, `$11 03 86 F4` | `lda $96E5,y` at `$96CF` supplies the PPUCTRL base (ORed with `$30`), `lda $96E7,y` at `$96D6` the palette number and `lda $96E8,y` at `$96DB` the palette source index, which `PaletteLoad` at `$96E1` then loads | | `$989A-$98DA` | 65 | `RunnerSpriteTileList` | A list of CHR tile indices for the runner figures: `$77`-`$8D`, a `$3C` separator, `$8E`, `$91`-`$94`, the three tiles `9`, `Y`, `Z`, then `$95`-`$AB`, another `$3C`, `$AC`, `$AF`-`$B2` and the same `9YZ` group again | `lda $989A,y` at `$9893` and `lda $98BA,y` at `$9882` supply tile indices to the sprite writers | | `$990B-$990D` | 3 | (unnamed) | **Unreached code** - `sta $0391 / lda $88 / clc / adc #$01` at `$990B` | no instruction in this dump references it | | `$9AEF-$9B22` | 52 | `RunnerSpriteParameterTable` | 13 four-byte records, each `(unused, $0F, graphic id, value)`: `$36 $14`, `$26 $30`, `$26 $16`, `$26 $21`, `$26 $28`, `$26 $1A`, `$26 $25`, `$26 $25`, `$17 $10`, `$36 $2C`, `$26 $28`, `$36 $2A`, `$17 $16` | `lda $9AF0,y` at `$999A`, `lda $9AF1,y` at `$99A0` and `lda $9AF2,y` at `$99A6` (Y = 4 x index) copy the three useful bytes into `$03AD`, `$03AE` and `$03AF` | | `$9BA1-$9BA4` | 4 | (unnamed) | **Unreached code** - `rts / jmp $C3A3` at `$9BA1` | no instruction in this dump references it | | `$9C7F-$9C88` | 10 | (unnamed) | **Unreferenced.** Ten bytes (`03 25 31 30 33 38 37 28 36 03`) that look like a short display row in the game's own tile encoding - `$03` is a separator and the rest are CHR tile indices - but no instruction in this dump reads them | no instruction in this dump references it | | `$9DCF-$9DFD` | 47 | (unnamed) | **Unreached code** - an object-state routine (`lda $0406 / cmp #$03 / beq ... / lda $0183 / ...` at `$9DCF`) | no instruction in this dump references it | | `$9E31-$9E64` | 52 | (unnamed) | **Unreached code** - the same shape as `$9DCF`, reached only through the event-task pointer table | no instruction in this dump references it | | `$9F33-$A02A` | 248 | (unnamed) | **Unreached code** - an object-state routine (`lda $0181 / bne ... / ldx $0408 / ...` at `$9F33`) | no instruction in this dump references it | | `$A034-$A04A` | 23 | (unnamed) | **Unreached code** - an object-state fragment (`jsr $A71A / lda #$00 / sta $0181` at `$A034`) | no instruction in this dump references it | | `$A547-$A56E` | 40 | `ObjectSpriteOffsetTable` | 40 single-byte pixel offsets, one per object type: `00 00 02 03 11 31 71 81 91 22 42 62 23 43 63 83 44 85 36 86 37 77 28 48 58 68 78 19 29 39 49 59 69 69 79 79 89 89 99 99` | `lda $A547,y` at `$A53C` (Y = object slot) adds the offset to the object's X position in `$0384` | | `$A5CC-$A613` | 72 | `EventObjectPositionTable` | 9 eight-byte records, each four (X, Y) coordinate pairs: `$B0 $B0 $6E $92 $26 $26 $2E $0E`, then the all-zero record for the second event, then `$FF 00 $04 00 $FD 00 $06 00 $FF 00 $08 00 $00 00 $00 03 00`, then `$00 00 $02 03 $FF 00 $04 03 $FD 00 $06 03 $FF 00 $08 03`, then two records of `$00 00 $C3 00 ...` | `lda $A5CC,y` at `$A573` and `lda $A5D0,y` at `$A578` (Y = `GameMode`) give the X and Y of the event's first object; the rest of the record supplies the other three | | `$A690-$A6EF` | 96 | `RunnerRankTable` | 96 single-byte values in three groups of 32, each group counting down (8, 8, 7, 7, 6, 6, 5, 5, 5, 5, 4 ... to 1, then 7 ... to 0, then 6 ... to 0) - the place each runner is given | `lda $A690,y` at `$958C` and at `$A676` (Y = the runner index from `$017B`) stores the rank in `Zp05` | | `$A71A-$A788` | 111 | (unnamed) | **Unreached code** - the routine that writes one object sprite to the OAM buffer. The block's bytes decode as instructions; the `lda $A779,y` the analyzer reports falls inside this code and is not a table read | no instruction in this dump references it | | `$A880-$A8C7` | 72 | `RunnerSpriteTileAttributeTable` | 9 eight-byte records, each three (tile, attribute) pairs followed by two zero bytes: `$C3 $CB $BB $CB $C3 $C5 $00 $00`, `$C4 $CC $BC $CC $C4 $C6 $00 $00`, and so on with both halves rising by one per record | `lda $A8C0,y` at `$A2F0` and `lda $A880,y` at `$A311` (Y = object type) supply the tiles of the three sprite pieces to `ObjectUpdate` and `Sub_A789` | | `$A9C7-$A9CF` | 9 | `DisplayAddressPairTable` | 9 bytes read as two parallel 3-byte arrays: the low bytes `$D0, $DA, $E4` at `$A9C7` and the high bytes `$E4, $67, $66` at `$A9CA`, plus a third group `$9C, $9D, $9E` at `$A9CD`. The ROM does not settle a cleaner record size for it | `lda $A9C7,x` at `$A955` and `lda $A9CA,x` at `$A95B` (X = 0-2) load the low and high byte of the display address into `$0424`/`$0425`; `lda $A9C7,x` at `$AD70` uses the same table for another display | | `$AC66-$AD0B` | 166 | `RunnerNumberTable` (`$AC66-$AC89`), `RunnerNametableAddressTable` (`$AC8A-$AD0B`) | `$AC66-$AC89` = 18 pairs of equal values (`00 00 01 01 02 02 02 03 03 04 04 05 05 06 06 07 07 07 0A 0A ... 11 11 11 11`) - the runner number drawn on each lane's name plate. `$AC8A-$AD0B` = 65 little-endian 16-bit nametable addresses, descending (`$F980`, `$F981`, `$F982`, `$F983`, `$F784`, ... ) | `lda $AC8A,x` at `$AA16` and `lda $AC8B,x` at `$AA1C` (X = 2 x index) load the nametable address of the runner's plate; `lda $AC66,x` at `$AA2D` gives the runner number | | `$ADE8-$AE13` | 44 | `ScoreDigitTileTable` (`$ADE8-$ADFD`), `ScoreDigitNametableAddressTable` (`$ADFE-$AE13`) | `$ADE8-$ADFD` = 22 CHR tile indices: `00 01 02 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14` and then the ten digit tiles `$93, $95, $97, $99, $9B, $9D, $A0, $A3, $A6, $A9`. `$ADFE-$AE13` = 16 little-endian nametable addresses (`$A9AC, $AEAE, $B0B0, ... `) | `lda $ADE8,x` at `$ADDF` (Y = `$20`) copies the tile row into the OAM buffer with `OamCopy` at `$ADE4`; `lda $ADFE,x` at `$ADD4` loads the nametable address of the digit into `$0424`/`$0425` | | `$B02A-$BE62` | 3641 | `NametableAddressTable` | A long list of little-endian 16-bit nametable addresses, descending from `$F890` (`$90 F8`) through `$F692`, `$F494`, `$F296`, `$F098` and on; the tail of the block holds the 32-byte per-lane sprite tables at `$BDC3` and `$BE43` | `ldy $B02A,x` at `$AE93` and `lda $B02B,x` at `$AE99` (X = 2 x object id) load the address of the object's nametable cell into `$0424`/`$0425`; `lda $BDC3,y` at `$A182` and `lda $BE43,y` at `$A1F3` copy the two 32-byte lane tables into `$039C` | | `$BFC4-$BFE7` | 36 | `AudioChannelDispatchPointerTable` (`$BFC4-$BFE3`), `AudioNoteHandlerPointerTable` (`$BFE4-$BFE7`) | 18 little-endian 16-bit values. Each stores the handler address **minus one**, because the sound engine pushes the address and an `rts` lands on it. `$BFC4-$BFE3` holds the 16 audio-channel entries, `$BFE4-$BFE7` the two entries the note player selects between | `lda $BFE5,x` at `$BFAA` and `lda $BFE4,x` at `$BFAE` (X = 2 x (note & 1)) push the handler address; the 16-entry table is reached the same way from `$BE77`/`$BE82` | | `$C0BE-$C0C5` | 8 | (unnamed) | **Unreferenced.** The IRQ vector at `$C0C0` points into the middle of the `jmp $C047` at `$C0BE` and the game never executes CLI, so the vector is dead; the region was forced back to data by hand | no instruction in this dump references it | | `$C434-$CF35` | 2818 | `AudioChannelParameterTable` | The audio driver's tables. `$C434-$C437` is code (`sta $4000 / rts`); from `$C438` on the block holds the per-channel state, the 16-entry note tables and the instrument data the `Sound*` routines read | `ldx $C438,y` at `$BE77` (Y = 2 x channel) indexes the per-channel data, `lda $C498,y` at `$BE82` and `lda $C499,y` at `$BE89` read the channel's note counters, `lda $CE36,y` at `$C06D` and `lda $CE37,y` at `$C072` read the instrument tables and `lda $CD9E,y` at `$C255` the envelope data | | `$D067-$D076` | 16 | `ObjectFlagTable` | 16 single-byte flag values (`12 06 22 0A 21 04 20 05 11 00 10 ...`), each carrying a 2-bit field in its low bits | `lda $D067,y` at `$D056` and again at `$D05C` - the low two bits pick one of four flag slots in `Zp32`, and the rest of the byte is ORed into it | | `$D11C-$D13A` | 31 | (unnamed) | **Unreached code** - an object routine (`ldy #$00 / lda $0414 / beq ... / lda ($10),y / ...` at `$D11C`) | no instruction in this dump references it | | `$D2BA-$D2BD` | 4 | `ControllerButtonMaskTable` | 4 single-byte masks (`$0F, $0A, $F0, $A0`), one per controller port and direction | `and $D2BA,x` at `$D26B` and `lda $D2BA,x` at `$D270` test the pressed-button bits of the port selected by X | | `$D317-$D32A` | 20 | `ControllerActionTable` | 16 pairs of action codes, one pair per combination of the four controller bits: `04 05, 1B 1C, 08 09, 08 0B, 0C 0D, 0E 0B, 0E 0A, 04 06, 07 0A, 07 0B` | `lda $D317,y` at `$D2CA` and `lda $D318,y` at `$D2D0` (Y = the high nibble of the button byte) store the two action codes into `$07E0`/`$07CE` | | `$D437-$D43A` | 4 | `EventStartParameterTable` | 4 single-byte values (`01 01 03 03`) | `lda $D437,y` at `$D376` (Y = `GameMode`) loads the event's starting parameter into `Zp26` | | `$D564-$D564` | 1 | (unnamed) | A single `$60` (RTS) byte, the tail of the routine that ends at `$D563` | no instruction in this dump references it | | `$D8BA-$D8F1` | 56 | `RunnerLaneSpriteParameterTable` | 7 groups of 8 CHR tile indices, one group per lane set: `03 37 38 36 37 2F 28 03`, `03 26 28 24 36 03 03 03`, `03 2B 31 36 35 28 03 03`, `03 36 24 26 26 2C 37 03`, `03 26 31 26 25 24 37 03`, `03 25 2B 28 28 37 24 2B`; `$03` separates the lanes | `lda $D8BA,x` at `$D8A8` (X = 0-7) copies the lane's tiles into `$0434`; `lda $D8BA,y` at `$9BDC` copies them into `$03C5` | | `$D900-$D906` | 7 | `LaneAttributeTable` | 7 single-byte CHR tile indices (`03 33 2F 24 3C 28 36`) - the lane markers drawn under the eight lanes | `lda $D900,y` at `$D8F4` (Y = 0-6) copies them into `$04F4` | | `$D9CF-$E0D4` | 1798 | `EventStatePointerTable` | A list of little-endian 16-bit pointers (`$DA31`, `$DA31`, `$DE29`, `$DE29`, `$DAC1`, `$DAC1`, ...) into the per-object state routines, followed at `$DDE9` by the eight per-lane progress records (`$98 00 00 00`, `$1F 00 00 00`, `$1F 00 00 00`, `$20 00 00 00`, `$1F 00 1F 00`, `$1F 00 20 00`, ...) | `lda $DDE9,y` at `$D4E6` (Y = lane) adds the lane's progress increment to `SlotState`; the pointer list itself is read by the object-state code reached through the event-task table | | `$E2A3-$E2B1` | 15 | (unnamed) | **Unreached code** - `lda #$03 / sta $042F / lda #$04 / sta $042C / lda #$02 / sta ...` at `$E2A3` | no instruction in this dump references it | | `$E5F2-$E609` | 24 | `EventFrameSchedulerPointerTable` | 12 little-endian 16-bit values, each a handler address **minus one** (the address is pushed and an `rts` lands on it) | `lda $E5F3,y` at `$E0DB` and `lda $E5F2,y` at `$E0DF` (Y = 2 x `OlympicsFrameCounter`) push the address of the routine that runs on that frame of the olympics | | `$E996-$EB03` | 366 | (unnamed) | **Unreached code** - the audio engine's own routines (`lda #$B0 / sta $10 / lda #$20 / sta $11 / ldx $0398 / ...` at `$E996`). The analyzer reports `lda $EA01,x` and `lda $EA4F,x` as reads of this block, but those operands land inside code and are not table reads | no instruction in this dump references it | | `$EF40-$EF4B` | 12 | `EventGoalParameterTable` | 12 single-byte values (`3C 3C 00 00 08 08 88 07 6C 01 DD ...`) - the distance or score each event's runner has to reach | `lda $EF40,x` at `$EECE` (X = `GameMode`) loads the event's goal value; a zero entry means the event has no goal | | `$EFFC-$F00B` | 16 | `EventPaletteTable` | 4 four-byte records: `20 04 25 F0`, `30 01 86 F4`, `30 05 A9 ...` - the PPUCTRL base, the palette number and the palette source of each of the four screens the game shows | `lda $EFFC,y` at `$EF5A`, `lda $EFFD,y` at `$EF5F`, `lda $EFFE,y` at `$EF63` and `lda $EFFF,y` at `$EF67` (Y = 4 x screen id) are handed to `PaletteLoad` at `$EF6C` | | `$F025-$F6FC` | 1752 | (unnamed) | **Unreferenced.** The block holds the game's glyph and message data - `BAC0123456789:;<=>?PQRQS` is in it - in the form of drawing scripts and tile streams. No instruction in this dump reads it: the routine that would consume it is inside the unreached code blocks | no instruction in this dump references it | | `$F796-$F79F` | 10 | `EventDispatchPointerTable` | 5 little-endian 16-bit addresses (`$D32B`, `$8004`, `$924C`, `$A8C8`, `$E0D5`) - the five task entry points the main loop dispatches to | `lda $F796,y` at `$F780` and `lda $F797,y` at `$F785` load the address and `jmp (Zp1F)` at `$F78A` jumps to it; the table is seeded by hand because the tracer cannot follow the computed jump | | `$F8E2-$F8E5` | 4 | (unnamed) | **Unreferenced.** Four bytes (`3C 7A B5 F3`) that decode as `bit $7A / lda $F3,x`; no instruction in this dump reads them | no instruction in this dump references it | | `$FA95-$FA9C` | 8 | `PaletteUpdateNametableAddressTable` | 4 little-endian 16-bit nametable addresses (`$9189`, `$9182`, `$98A9`, `$98B1`) | `lda $FA95,x` at `$FA3E` supplies the low byte of the PPU address the NMI palette update writes to | | `$FC78-$FFFF` | 904 | (unnamed) | **Unreferenced.** `$FF` fill from `$FC78` to `$FFF9`, then the three interrupt vectors at `$FFFA-$FFFF` (NMI, RESET, IRQ). No instruction in this dump reads it; the vectors are read by the CPU | no instruction in this dump references it | ## Entry points that are still named after their address **91 of the 233 address-derived entry points are still named `Sub_`.** The other 142 now carry a name taken from what the ROM shows them doing, and their `; Global:` lines were rewritten to say so. The residue is grouped below by *why* the dump does not reveal a better name. | Address | Why the dump does not reveal a better name | |---------|------------------------------------------| | `$8118`, `$81D3`, `$82BA`, `$8358`, `$8D45`, `$91CF`, `$92E2`, `$93BD`, `$9453`, `$94FF`, `$95B2` | the event-task state handlers. Each one is reached from the event-task pointer table at `$859B` and branches on the slot's state byte (`Zp04`) and on the event number (`GameMode`), but the dump never says in words which state of which event the handler implements - the states are plain numbers with no table that names them | | `$8409`, `$84C5`, `$8581`, `$8627`, `$8677`, `$8ADE`, `$9631`, `$981D`, `$9D7B`, `$A31A`, `$A37E`, `$A3BC`, `$A48C`, `$A705`, `$D686`, `$D738`, `$D770`, `$D7CF`, `$E20D`, `$E2DE`, `$E52C`, `$E625` | flag and mode tests. The code compares `GameMode`, `Zp0F`, `Zp08`, `Zp09` and a handful of RAM bytes against small constants, and the game does not name those constants | | `$874E`, `$875B`, `$879B`, `$87AA`, `$8868`, `$88A8`, `$88F9`, `$8910`, `$8A08`, `$8ABC`, `$8D18`, `$8D42` | the per-object setup steps of the event tasks. They place one of the event's objects at a position, give it a tile and a state, and hand it to the sprite code; the dump shows the arithmetic but not which of the event's objects each step places | | `$87EA`, `$87F6`, `$8908`, `$8A08` | the sprite-animation and lane-offset steps. The lane's horizontal offset is a signed byte in `$0474` and the dump shows the offsets being added and cleared, but not what the offset is measured from | | `$83C6`, `$94BE` | the two scroll-setup routines. They fill `$03BE`-`$03C4` with the scroll values of the track, but the dump does not settle which of those bytes is the fine scroll, the coarse scroll and the nametable bits | | `$8ADE`, `$8D42` | one is a `jmp` trampoline to the other; both sit in the event-task code that the pointer table reaches | | `$91CF`, `$8A08`, `$8D18` | three-instruction stubs that set one byte of the object's state and jump back into the event-task chain | | `$A4E4`, `$A614`, `$A635`, `$A649`, `$A68A`, `$A789`, `$A7AC` | the per-lane helpers of the event code: they read the lane's index out of the lane table, keep the lane's progress counters and write the lane's sprite. The dump shows the table reads but not the game's word for the lane's progress | | `$AD0C`, `$AD86`, `$CF36`, `$CF97`, `$D077`, `$D08E`, `$D25D`, `$D43B`, `$E4EB`, `$E90B`, `$F9E0`, `$FABB` | the score, palette and object-placement helpers. The dump shows the arithmetic on the score and the palette bytes, but the game does not name the screen they belong to | | `$9B5A`, `$9C33`, `$9C9B`, `$D80F`, `$D855` | the shared "look a value up in a table" helpers; each one walks a table with a signed index and the dump does not say which of the game's tables each of them walks | | `$9BF5`, `$9B23`, `$9964`, `$99CC`, `$9A41`, `$9A80`, `$9941` | the seven per-lane sprite writers of the event screen. Each one loads the lane's sprite parameters and writes them into the OAM buffer; the dump shows the parameters but not which runner each writer belongs to | | `$98DB`, `$9910` | the two lane-progress counters (`$0391` and `$0392`); the dump shows them being counted and compared with each other, but not what they measure | | `$9D0E`, `$C0F8`, `$C3C2` | three-instruction stubs at the head of larger routines that the tracer split at the wrong byte | | `$D6F4`, `$E2B2` | the round and event counters of the olympics sequence; the dump shows the counting but not the game's name for the round | ## Open work - **Promotion** to `NROM//` once ready, as `git mv` (all four milestones Pass 1/2/3 are done; Promotion is the only remaining step). - The event-task routines at `$8DA5-$9140` are reached only through the pointer table at `$859B`, which the tracer cannot follow, so they are still filed as data. Re-seeding the tracer with those twelve addresses would turn them into ordinary code and shrink the data blocks. - `$95D8`/`$95F8` and `$F025-$F6FC` still have no consumer inside this dump; their contents are described in the inventory, but the instruction that reads them has not been located. ## Files | File | Purpose | |------|---------| | `prg.s` | Pass 3 disassembly (8,400 instructions + 55 data blocks, all labels named) | | `routinemap.tsv` | Pass 2 address → name map (884 entries) | | `header.s` | iNES header include | | `chr.s` | CHR include | | `prg.bin` | 32,768 bytes (32 KB) PRG image | | `chr.bin` | 32,768 bytes (32 KB) CHR image | | `rom.nes` | byte-identical copy of the original ROM | | `linker.cfg` | HEADER / PRG / CHR memory areas, PRG at $8000 | | `symbols.inc` | NES hardware register names | | `locals.inc` | zero-page aliases (24 semantic + 78 neutral `ZpXX` with reasons) | | `Makefile` | build, byte-identity check, and `make pass1` |