# Duck Hunt (JUE) — Promoted to Top Level (2026-10-19) ROM: `Duck Hunt (JUE).nes` (24,592 bytes, iNES). Status: **Pass 1 complete; Pass 2 complete; Pass 3 data tables interpreted; Promoted to top level 2026-10-19.** `prg.s` is a real disassembly: the code/data split, every instruction carrying a trailing audit comment, the routine labels named, and **23 data tables named** from the 10,232-byte data region - all 28 data blocks are listed with their structure and their consuming instruction in the **Pass 3 data-table inventory** below. It builds **byte-identical** at the whole-ROM level (`cmp reconstructed.nes rom.nes`). Every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name as well: the duck and dog logic is named for what it does (`UpdateDuckSprites`, `SelectDuckSprite`, `CheckDuckCollision`, `ProcessDuckScript`, `UpdateDogSprites`, `ComputeDuckHitBox`, ...), the graphics side for what it draws (`DrawPpuBlockList`, `DrawQueuedPpuBlock`, `QueuePpuBlocks`, `HideAllSprites`, ...), the score code for what it counts (`UpdateScoreDigits`, `AddBcdValue`, `SubtractBcdValue`) and the sound engine for what it plays (`UpdateSoundEngine`, `LoadSoundEffectRecord`, `UpdateMusicChannels`, `SilenceApuChannels`). No entry point is left with an address-derived name. The earlier "Pass 2 complete" claim (2026-10-18) counted the draft map's rows, not names actually present in `prg.s`. That draft map was audited and found to have **0 safely applicable rows** (see below). Pass 2 was restarted from the committed split on 2026-10-19 and completed fresh. Pass 3 (data table naming + full ZP substitution) completed same day. Promotion to top level completed 2026-10-19. ## 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 `$C000` (`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 | |---------|---------| | `$C002` | `WaitVBlank` (falls through) | | `$C01E` | `ClearZeropage` (falls through) | | `$C02C` | `InitGameState` (falls through) | | `$C22B` | `UpdateSprites_4` | | `$C23B` | `HideAllSprites` | | `$F581` | `ResetNoiseChannel` | | `$C056` | `InitVariables` (falls through) | | `$C232` | `DrawPpuBlockByIndex` | **NMI** is at `$C086` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$C2BF` | `DrawPpuBlockList` | | `$C249` | `DrawQueuedPpuBlock` | | `$C263` | `DrawSpritePpuBlock` | | `$C3C1` | `WriteScrollPosition` | **IRQ** is at `$C000` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path. ## 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 | Vertical | | Battery-backed flag | clear | | ROM SHA-256 | `ca0d31e6f3aa70204fa5af67803c6533851a28fa48a133e15d772d3667fa675a` | | PRG SHA-256 | `bcdd22fbe343ebd33c7fd19309510079150d1e4dc052f65382b64d197d6ba080` | ## Interrupt Vectors (taken from PRG $3FFA–$3FFF) | Vector | Target | Note | |--------|--------|------| | NMI | `$C086` | per-frame heartbeat | | RESET | `$C000` | cold start at $C000 — the usual pattern in this repo | | IRQ | `$C000` | redirects to RESET — unused placeholder | ## Project Contents | File | Purpose | |------|---------| | `Duck Hunt (JUE).nes` | original ROM image (reference) | | `prg.bin` / `header.bin` / `chr.bin` | split image, produced by `_tools/splitnes.py` | | `rom.nes` | symlink to `../Duck Hunt (JUE).nes` (space-free reference for `cmp`) | | `prg.s` | Pass 1/2/3: classified code/data split, 3,058 instructions, **472 labels all named** (30 semantic + 428 `SubC` + 293 `_Block`), canonical audit comments | | `symbols.inc` | shared NES hardware constants (PPU/APU/controller) | | `locals.inc` | 174 zero-page slots, **18 semantic aliases** (e.g., `PtrLo/PtrHi`, `Ptr2Lo`, `GameState`, `SndTemp`, `SndChannel`, `SndNote`, `SprData`, `Temp`, `CtrlState`, `CtrlTimer`, `VramPtrLo/Hi`, `DataPtrLo`, `SndChan`, `SndPtrLo/Hi`, `FrameCounter`) | | `duckhunt_label_map.tsv` / `duckhunt_full_map.tsv` | Pass 2 draft maps — **stale and multi-candidate**, see the audit below; kept for provenance only | | `header.s` / `chr.s` | one-line `.incbin` wrappers for `header.bin` / `chr.bin` | | `linker.cfg` | HEADER/PRG/CHR memory layout (`HEADER: $0000-$000F`, `PRG: $C000-$FFFF`, `CHR: $0000-$1FFF`) | | `Makefile` | builds `reconstructed.nes` and `cmp`s it against `rom.nes` | | `reconstructed.nes` | build output from the last verified build | Header and CHR are now linked: `make` verifies **whole-ROM byte identity** against `rom.nes`. ## Build ```bash cd "NROM/Duck Hunt (JUE)" make # → cmp reconstructed.nes rom.nes → "Byte-identical ROM verified." ``` ## Pass Status | Pass | Status | |------|--------| | Pass 1 | **Complete** — 3,058 instructions, 6,152 bytes as code (37.5%), 10,232 bytes as data, byte accounting exact (16,384) | | Pass 2 | **Complete** — 472 label definitions all named (30 semantic + 428 `SubC` + 293 `_Block`); 174 zero-page slots with 18 semantic aliases in `locals.inc` | | Pass 3 | **Data tables interpreted** — **23 data tables named** (`GameModeHandlerPointerTable`, `DuckSpeedTable`, `DuckFlightParameterTable`, `DuckFlightPathTable`, `NoisePeriodTable`, `TriangleNoteTable` and the rest), and every one of the 28 data blocks listed with its structure and its consuming instruction in the **Pass 3 data-table inventory** below. The generic names this project carried before the 2026-10-21 re-split (`SpriteDataTable6`, `FontData`, `BackgroundTileData` and the rest) are gone: the table is now read for what the bytes are | | Promotion | **Complete (2026-10-19)** — added `header.s`/`chr.s` + HEADER/PRG/CHR `linker.cfg`, switched `make` to whole-ROM `cmp`, moved `_tools` paths | ## Pass 2 Status and Map Audit (corrected 2026-10-19) The earlier "Pass 2 complete" text was wrong on every number it gave, because it read the draft map instead of the source: - **"48 semantic labels"** was the number of *distinct old names* in the map (`duckhunt_full_map.tsv` has 272 `label` rows covering only 48 distinct `Label_` names). `prg.s` actually named **31** definitions; the other 441 were still `Label_`. - **"174 zero-page semantic aliases"** was the count of **neutral** `ZpXX` entries in `locals.inc`. There were **zero** semantic zero-page aliases. - Several listed "key renamed routines" (`InitPPU`, `MainGameLoop`, `GameModeCheck`, `TitleScreenMode`, `UpdateSprites`, `InitSound`) did not exist in the file under those names. Draft-map audit (`duckhunt_full_map.tsv`, 272 label rows): | Outcome | Rows | Meaning | |---|---|---| | already applied | 31 | the proposed name already sits at that address | | name conflict | 210 | a **different** candidate name already sits there — the map is a ranked *candidate* list (one `Label_C2BF` carries 205 competing names) | | stale address | 2 | the address is not an instruction boundary in the current `prg.s` | | unlabelled, unreferenced | 29 | an instruction boundary with no label and no operand referencing it | | **safely applicable** | **0** | no row is both insertable and corroborated by control flow | One row in the file is malformed (`label_Label_C2BF`, missing tab). The map is therefore kept only as provenance. Completing Pass 2 needed a fresh map derived from the committed split (routine entries plus `_Block[_n]` internal branch targets, as done for `Donkey Kong (JU)`). **This was done on 2026-10-19.** ### Pass 2 Naming Summary (2026-10-19) - **30 semantic routine names** preserved from Pass 1: `Reset`, `WaitVBlank`, `ClearZeropage`, `InitGameState`, `InitVariables`, `MainGameLoop_1`, `MainGameLoop_3`, `NmiHandler`, `NmiFrameEnd`, `GameModeDispatcher`, `TitleScreenLoop`, `InitializeRoundData`, `InitializeDuckDataLoop`, `UpdateZp09_2`, `UpdateGameState`, `UpdateGameState2`, `UpdateGameState3_1`, `UpdateGameState6_3`, `UpdateSprites_4`, `UpdateSprites2_1`, `UpdateSprites2_3`, `UpdateSprites3`, `UpdateController`, `UpdateController2`, `UpdateController3`, `UpdateController4_206`, `GameModeCheck_3`, `GameplayFrame`, `UpdateGameState3_3`, `UpdateGameState5`. - **428 `SubC` entry points** for all remaining `jsr`/`jmp` targets that lacked semantic names (e.g., `SubCC14E` at $C14E, `SubCC303` at $C303, etc.). - **293 `_Block[_n]` internal branch targets** derived from control flow within each routine (e.g., `Reset_Block`, `GameModeDispatcher_Block`…`GameModeDispatcher_Block_5`, etc.). ### Zero-Page Semantic Aliases (18, added 2026-10-19) | Alias | ZpXX | Address | Role | |---|---|---|---| | `PtrLo` | `Zp00` | $00 | general-purpose indirect pointer low byte | | `PtrHi` | `Zp01` | $01 | general-purpose indirect pointer high byte | | `Ptr2Lo` | `Zp0C` | $0C | secondary indirect pointer low byte | | `GameState` | `Zp09` | $09 | game state/mode (InitGameState, InitializeDuckDataLoop, etc.) | | `SndTemp` | `Zp0F` | $0F | sound engine temporary | | `SndChannel` | `Zp14` | $14 | sound channel index | | `SndNote` | `Zp15` | $15 | sound note/frequency | | `SprData` | `Zp23` | $23 | sprite data pointer | | `Temp` | `Zp25` | $25 | general-purpose temporary | | `CtrlState` | `Zp31` | $31 | controller state | | `CtrlTimer` | `Zp32` | $32 | controller timer/repeat counter | | `VramPtrLo` | `Zp96` | $96 | VRAM pointer low byte | | `VramPtrHi` | `Zp97` | $97 | VRAM pointer high byte | | `DataPtrLo` | `ZpB4` | $B4 | data pointer low byte | | `SndChan` | `ZpEB` | $EB | sound channel | | `SndPtrLo` | `ZpEC` | $EC | sound pointer low | | `SndPtrHi` | `ZpED` | $ED | sound pointer high | | `FrameCounter` | `ZpF3` | $FF | frame counter | ## Pass 3 data-table inventory Every `; ---- data $XXXX-$YYYY ----` region in `prg.s` is listed here, one row per region, with the bytes' structure and the instruction that reads it. A region that is only partly table and partly code says so: the tracer's data blocks are contiguous runs of bytes it never reached, so a block can hold a table, a table *and* unreached code, or code alone. ### How the blocks were classified * the **Mesen CDL capture** (`duckhunt_gameplay.cdl`). Its Data flag marks every byte the CPU read without fetching it as an opcode, its Code flag every byte it fetched as an opcode. A block whose bytes are flagged Data is a table; a block flagged Code is code the static tracer could not reach. * the **tracer's own decode**. A block that decodes as a long run of instructions with coherent operands is code, not data. * the **consuming code**. Every instruction whose operand lands inside a block was read, including the indexed forms (`lda $E64B,x`) the analyzer's own report misses, and the indirect forms. ### The dispatch mechanism The dispatcher at $C35E is the game's computed dispatcher. It takes the handler index in A, doubles it, pulls the return address of the `jsr $C35E` that called it, and reads a 16-bit pointer out of the inline table that follows that call: ``` $C35E: stx Zp28 / sty Zp29 asl a / tay / iny ; Y = 2*index + 1 pla / sta Zp14 / pla / sta Zp15 ; Zp14/Zp15 = the address after the call lda (Zp14),y / tax / iny / lda (Zp14),y sta Zp15 / stx Zp14 ldx Zp28 / ldy Zp29 jmp (Zp14) ``` So a `jsr $C35E` followed by a run of bytes is a **handler pointer table**. The CDL shows which of those bytes the game actually read: at $CCAB it read exactly $CCB0-$CCB5 (three pointers), and at $C125 the table at $C128. ### Inventory | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C128-$C14D` | 38 | `GameModeHandlerPointerTable` | 19 x 16-bit LE handler pointers ($C81D, $C839, $C854, $C86E, $C8C9, $D2E0, $C579, $C72C, $C74B, $D56A, $D593, $D5D2, $D2C9, $C7EF, $C5E0, $C5E0, $C5E0, $C271, $C625), the inline table that follows `jsr $C35E` at $C125 | indirectly, by `lda (Zp14),y` at `$C36B` and `$C36F` | | `$C227-$C22A` | 4 | - | unreferenced; two 16-bit LE values ($C5AA, $C5E0) that are the addresses of two of the handlers in the table above - the tail of an inline dispatch table whose start the tracer lost | not referenced by any instruction | | `$C511-$C587` | 119 | - | unreferenced; decodes as 63 consecutive instructions (a routine that moves the six bytes of a 12-bit position around the zero page) - code the tracer could not reach | not referenced by any instruction | | `$C5AA-$C5D8` | 47 | - | unreferenced; the CDL flags all 47 bytes as Code - code the tracer could not reach | not referenced by any instruction | | `$C72C-$C792` | 103 | - | unreferenced; decodes as a long run of instructions - code the tracer could not reach | not referenced by any instruction | | `$C7EF-$C7FA` | 12 | - | unreferenced; decodes as 6 instructions (`lda Zp9D` / `bne` / `jmp` / `lda #$0A` / `sta Zp25` / `rts`) - code the tracer could not reach | not referenced by any instruction | | `$CCB0-$CCB6` | 7 | `StateHandlerPointerTable` | `$CCB0-$CCB5` = three 16-bit LE handler pointers ($CCD4, $CD16, $CD1D) of the inline table that follows `jsr $C35E` at $CCAB - the CDL marks exactly those six bytes as Data, so entry 0 (at $CCAE, which the tracer decoded as `cpx #$C5`, that is the pointer $C5E0) was never used; `$CCB6` = a stray byte before the code at $CCB7 | indirectly, by `lda (Zp14),y` at `$C36B` and `$C36F` | | `$CCBD-$CCD3` | 23 | `StateHandlerPointerTableAlt` | 11 x 16-bit LE pointers ($CD52, $CD5C, $CD6C, $CE4D, $CD80, $CE4D, $CD9A, $CE18, $CE2E, $CE4D, $CE40) to the game-mode handlers | not referenced by any instruction - reached through the computed dispatcher | | `$CD1D-$CD2C` | 16 | - | unreferenced; the CDL flags 14 of its bytes as Code - code the tracer could not reach | not referenced by any instruction | | `$CD2E-$CD43` | 22 | - | unreferenced; the CDL flags 9 of its bytes as Code - code the tracer could not reach | not referenced by any instruction | | `$CD6C-$CE17` | 172 | - | unreferenced; decodes as a long run of instructions - code the tracer could not reach | not referenced by any instruction | | `$CE6D-$CE71` | 5 | - | unreferenced; the tail of the inline table that follows `jsr $C35E` at $CE63 - the CDL marks $CE6D-$CE6E as Data (the entry the game used) and the rest as untouched | not referenced by any instruction | | `$CF00-$CF0A` | 11 | - | unreferenced; decodes as 5 instructions - code the tracer could not reach | not referenced by any instruction | | `$D2C9-$D3D1` | 265 | - | unreferenced; decodes as a long run of instructions - code the tracer could not reach | not referenced by any instruction | | `$D56A-$D5D1` | 104 | - | unreferenced; decodes as a long run of instructions - code the tracer could not reach | not referenced by any instruction | | `$D66A-$D66A` | 1 | - | a single stray byte `$4F` between two instructions; not referenced by any instruction | not referenced by any instruction | | `$DA0A-$DAA4` | 155 | - | unreferenced; decodes as a long run of instructions - code the tracer could not reach | not referenced by any instruction | | `$DAC5-$F35D` | 6297 | `DuckGraphicsDescriptionData` | the game's graphics and sprite-description area: 6,297 bytes that hold several distinct kinds of data. At $DAC5 a 38-entry 16-bit LE pointer table; from $DCE3 a region of 4-byte sprite records; from $E000 a long "opcode + operands" script that places tiles on the nametable; a second script in the same format that describes the duck's eight animation frames; the screen-layout data in the `$21 $D8 $B7` form; a bitmap region of `$FF` bytes; and at the end a table of 16-bit LE values used as screen positions. The 23 tables inside it that the code reads by name are listed in the rows below | the block is read by 40 instructions; the ones with a named sub-table are listed below | | `$E5DB-$E5DC` | 2 | `DuckSpriteCoordinate` | a 16-bit LE pair: Y = $23, X = $00 | `ldx $E5DC` at `$C673` and `ldy $E5DB` at `$C676`; the pair becomes Zp00/Zp01 for the sprite placement routine at the routine at $C71D | | `$E5DF-$E5EA` | 12 | `DuckStateInitialValues` | 3 x 4-byte records, each (00, 01, 20, 00) | `lda $E5DF,x` at `$C056`, copied to $05F0-$05FB | | `$E532-$E53B` | 10 | `DuckFlightPathPointerTable` | 5 x 16-bit LE pointers to the duck's flight-path data ($E540, $E558, $E57C, $E5A0, $E5A9) | `lda $E532,y` at `$C251` and `ldx $E533,y` at `$C254` with Y = 2*(the flight pattern number) | | `$E5EB-$E5F6` | 12 | `PlayerStateInitialValues` | 12 x 8-bit initial values (01, 01, $A8, 80, 00, 00, 00, $3F, 01, 00, 03, 00) for the player's state block at $30-$3B | `lda $E5EB,x` at `$C91B`, copied to Zp30-Zp3B | | `$E60B-$E612` | 8 | `SpriteDataPointerTable` | 4 x 16-bit LE pointers ($ED76, $ED98, $EDB0, $EDBC) to the sprite's tile/shape data | `lda $E60B,x` at `$C95C` and `lda $E60C,x` at `$C961` with X = the index * 2; the pair becomes the Zp44/Zp45 pointer that `lda (Zp44),y` at `$C971` reads | | `$E64B-$E662` | 24 | `DuckSpeedTable` | 24 ascending 8-bit speeds (00, 03, 10, 17, 24, 27, 34, 3B, 48, 4B, 58, 5F, 6C, 6F, 7C, 83, 90, 95, 98, 9B, 9E, A1, ... FF) | `lda $E64B,x` at `$CB7A`; the value becomes Zp49 and Zp4A, the duck's speed | | `$E8DA-$E8E1` | 8 | `PlayerStateInitialValuesAlt` | 8 x 8-bit initial values (01, 02, 00, 00, 00, 00, 00, $4F) | `lda $E8DA,x` at `$D66B`, copied to Zp30-Zp37 | | `$E968-$E977` | 16 | `DuckFlightParameterTable` | 4 x 6-byte flight records: each holds a pattern number, a zero byte and two 16-bit values (e.g. 00 00 C0 15 1E 2C) | `lda $E968,x` .. `lda $E96D,x` at `$D6A1`-`$D6BA` with X = 6*(the pattern number); the six bytes become Zp44/Zp43, Zp4A/Zp42 and Zp47/Zp4C | | `$EB30-$EB37` | 8 | `SpriteDataPointerTableAlt` | 4 x 16-bit LE pointers ($EB4A, $EB5D, $EB70, $EB7D) | `lda $EB30,y` at `$C680` and `lda $EB31,y` at `$C685` with Y = the actor type * 2 | | `$EC1F-$EC38` | 26 | `SoundEffectScriptTable` | a script of `[count][value]` records terminated by `$AA`; the reader at $C7FB walks it, taking the byte after the count as the sound id | `lda $EC1F,y` at `$C803` and `$C80C` | | `$EC38-$EC3F` | 8 | `SoundEffectPointerTable` | 4 x 16-bit LE pointers ($EC4A, $EC70, $EC82, $EC8B) to the sound-effect data | `lda $EC38,x` at `$D472` and `lda $EC39,x` at `$D477` with X = the sound id * 2 | | `$DCC0-$DCC7` | 8 | `DuckSpriteXOffsetTable` | 8 x 8-bit sprite X offsets (00, 00, 08, 00, 10, 00, 18, 00) | `lda $DCC0,x` at `$D0BB` and `$D0F2`; the value is added to the object's base X and written to the OAM shadow | | `$F300-$F341` | 66 | `DuckFlightPathTable` | a table of 16-bit LE values - the duck's horizontal path - in the form `00 D4 00 C8 00 BD 00 B2 00 A8 00 9F 00 96 00 8D 00 85 00 7E 00 70 01 AB 01 93 ...` | `lda $F300,y` at `$F55B` and `lda $F301,y` at `$F553`; `lda $F350,y` at `$F54A` reads the companion table at $F350 | | `$F3DD-$F40E` | 50 | `TriangleNoteTable` (`$F3E1`), `NoiseEffectTable` ($F400) | `$F3E1-$F3F0` = 14 x 8-bit triangle-channel periods (4F, 53, 58, 5E, 63, 69, 70, 76, 7E, 85, 8D, 90, 92, 93) for the 14 notes of a phrase; `$F400-$F40E` = 5 x 3-byte records of (noise control, noise period, length) | `lda $F3E1,y` at `$F604` (written to APUTRIANGLETIMERLO); `lda $F400,y` at `$F509`, `lda $F401,y` at `$F50F` and `lda $F402,y` at `$F515` (written to APUNOISECTRL, APUNOISEPERIOD and APUNOISELENGTH) | | `$F5B7-$F5BE` | 8 | - | unreferenced; four 16-bit LE values ($7F9F, $0060, $7F9F, $0061) that sit in the audio data | not referenced by any instruction | | `$F6CB-$F6CE` | 4 | `NoisePeriodTable` | 4 x 8-bit noise-channel periods (00, 02, 01, 00) | `lda $F6CB,y` at `$F6B6`, written to APUNOISEPERIOD | | `$F6FF-$F702` | 4 | - | unreferenced; four 8-bit values (3F, 7F, 0F, 08) in the audio data | not referenced by any instruction | | `$F731-$F790` | 96 | `NoisePeriodTableAlt` ($F731), `NoiseControlTable` ($F761) | two 48-byte tables for the drum: `$F731-$F760` = 48 noise periods (0E, 0B, 05, 05 ... 00) and `$F761-$F790` = 48 noise control values (3F, 3F, 3F, 3F, 3F, 3F, 3D, 3C ... 32) | `lda $F731,y` at `$F711` (written to APUNOISEPERIOD) and `lda $F761,y` at `$F717` (written to APUNOISECTRL) | | `$F7A0-$F7A7` | 8 | - | unreferenced; four 16-bit LE values in the audio data | not referenced by any instruction | | `$F847-$F84C` | 6 | `SoundInstrumentParameterTable` | 6 x 8-bit instrument parameters (13, 1C, 25, 2E, 37, 40) | `lda $F847,y` at `$F38E` .. `lda $F84C,y` at `$F3A7`; the six bytes become ZpF8-ZpFD of the sound engine's instrument state | | `$F859-$F862` | 10 | - | unreferenced; ten bytes of audio stream data | not referenced by any instruction | | `$F86B-$F875` | 11 | - | unreferenced; eleven bytes of audio stream data | not referenced by any instruction | | `$F885-$FFFF` | 1915 | - | the audio engine's data area: the music and sound-effect command streams, the note tables and the waveform data, ending with the 6502 vectors at $FFFA-$FFFF | `sbc $F91B,y` at `$F856`; the rest of the area is walked by the audio routines | ### Notes on the blocks that are not tables - The CDL capture settles the code/data question for the blocks the CPU actually touched: `$C5AA-$C5D8`, `$CD1D-$CD2C` and `$CD2E-$CD43` are flagged Code, while `$C128-$C14D`, `$CCB0-$CCB6`, `$CE6D-$CE71`, `$F847-$F84C`, `$F859-$F862` and `$F86B-$F875` are flagged Data. The blocks the capture never reached at all are classified from their decode alone, and every one of them decodes as a coherent run of instructions. - The 6,297-byte region `$DAC5-$F35D` is the one block whose layout is only partly established. Its 23 tables that the code reads by name are named and listed above; the script and layout data between them is described in the row itself, and its finer format is not established from this dump. - `$C227-$C22A` and `$CE6D-$CE71` are the ragged tails of two inline dispatch tables: the tracer's block boundaries cut them at an instruction boundary the dispatcher does not use, so the pointers they hold are recorded with the rest of the table rather than named separately. ## Power-On Flow Analysis **Reset ($C000)** → `sei`/`cld` → PPU init (`sta PPUCTRL` #$00) → stack init (`ldx #$FF`/`txs`) → `WaitVBlank` (poll `PPUSTATUS` for vblank) → `ClearZeropage` (clear $0000–$00FF via `(Zp00),y`) → `InitGameState` → `InitVariables` → `MainGameLoop_1` → NMI synchronization → `GameModeDispatcher` dispatch loop. **NMI ($C086)** → saves registers → PPU status read → sprite DMA via `OAMDMA` → scroll update (`PPUSCROLL`) → PPU mask/ctrl updates → `GameModeDispatcher` dispatch → restores registers → `rti`. **IRQ ($C000)** → redirects to RESET (unused placeholder). **Main Game Loop** → `MainGameLoop_1` / `MainGameLoop_3` → calls `GameModeDispatcher` which dispatches to game mode handlers → loops. **Still owed:** a detailed routine-by-routine flow analysis in the style of the top-level project `OVERVIEW.md`s.