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

11 KiB
Raw Blame History

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 cmps 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

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.mds.