# Duck Hunt (JUE) — Promoted to Top Level (2026-10-19) ROM: `Duck Hunt (JUE)/Duck Hunt (JUE).nes` (24,592 bytes, iNES). Status: **Pass 1 complete — 37.5% code; Pass 2 complete; Pass 3 complete; Promoted to top level 2026-10-19.** `copilot/prg.s` is a real disassembly: 3,058 instructions (6,152 bytes as code), **472 label definitions all named** — 30 semantic routine names + 428 `SubC` entry points + 293 `_Block[_n]` internal branch targets. `locals.inc` holds **174 zero-page slots with 18 semantic aliases** added. All 18 multi-alias ZP operands substituted in `prg.s`. **20 data tables/structures named** in the 10,232-byte data region. It builds **byte-identical** at the whole-ROM level (`cmp reconstructed.nes rom.nes`). 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. ## 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 | ## `copilot/` 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 "Duck Hunt (JUE)/copilot" 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 | **Complete** — **18 multi-alias ZP operands fully substituted** in `prg.s`; **20 data tables/structures named** across all data regions; hardware register operands (68) substituted via `apply_symbols.py` | | 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 Naming (2026-10-19) **20 data tables/structures named** across all data regions: | Label | Address Range | Size | Description | |---|---|---|---| | `GameState3_1_Data` | $C12C–$C14B | 32 bytes | UpdateGameState3_1 data | | `SpriteAnimTable` | $C511–$C580 | 112 bytes | Sprite animation table (7 entries × 16 bytes) | | `SpriteAnimTable2` | $C5AA–$C5C9 | 32 bytes | Sprite animation table 2 (2 entries) | | `SpriteDataTable` | $C72C–$C7EB | 196 bytes | Sprite data table (13 entries) | | `DuckDataTable` | $D2A4–$D3C3 | 192 bytes | Duck data table (12 entries) | | `SpriteDataTable2` | $D56A–$D5C9 | 96 bytes | Sprite data table 2 (6 entries) | | `LevelData` | $DAC5–$F355 | 6,289 bytes | Level/background data | | `SpritePtrTable` | $DCC0–$DCCF | 16 bytes | Sprite pointer table (8 pointers) | | `SpriteOffsetTable` | $E532–$E541 | 16 bytes | Sprite offset table | | `GameInitData` | $E5DF–$E5EA | 12 bytes | Game initialization data | | `SpritePosTable` | $E5DB–$E5E2 | 8 bytes | Sprite position table | | `ControllerTable` | $E60B–$E612 | 8 bytes | Controller state table | | `SpritesTable` | $E661–$E66E | 14 bytes | Sprites table | | `SpriteDataTable3` | $E73C–$E74B | 16 bytes | Sprite data table 3 | | `SpriteDataTable4` | $E75A–$E765 | 12 bytes | Sprite data table 4 | | `SpriteDataTable5` | $E766–$E775 | 16 bytes | Sprite data table 5 | | `SpriteAnimData` | $EB30–$EB3F | 16 bytes | Sprite animation data | | `SpriteAnimData2` | $EBFA–$EBF9 | 10 bytes | Sprite animation data 2 | | `SoundData` | $EC1F–$EC2E | 16 bytes | Sound data | | `SpritePtrTable` | $DCC0–$DCCF | 16 bytes | Sprite pointer table (8 pointers) | | `SpriteDataTable6` | $E92A–$E939 | 16 bytes | Sprite data table 6 | | `SpriteDataTable7` | $E944–$E953 | 16 bytes | Sprite data table 7 | | `SpriteDataTable8` | $E968–$E977 | 16 bytes | Sprite data table 8 | | `SpriteDataTable9` | $E9C8–$E9D7 | 16 bytes | Sprite data table 9 | | `SpriteDataTable10` | $EA30–$EA3F | 16 bytes | Sprite data table 10 | | `FrameData` | $F300–$F34F | 80 bytes | Frame data | | `FontData` | $F350–$F35F | 16 bytes | Font/glyph data | | `BackgroundTileData` | $F847–$FFF6 | 1,968 bytes | Background tile data | All data regions now have descriptive labels; the byte-identity audit trail is preserved. ## 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.