Files
disasm/Duck Hunt (JUE)/OVERVIEW.md
T
tmerritt 13bd782a90 Donkey Kong Jr Math Promoted
Duck Hunt Promoted
Ice Climber WIP
2026-10-19 16:24:56 -04:00

159 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<addr>` entry points + 293 `<Routine>_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<addr>` + 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<addr>` + 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 `<Routine>_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<addr>` entry points** for all remaining `jsr`/`jmp` targets that lacked semantic names (e.g., `SubCC14E` at $C14E, `SubCC303` at $C303, etc.).
- **293 `<Routine>_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.