Promote Ice Climber (JE) and Ice Climber (U) to top-level - git mv _wip/Ice Climber (JE) -> Ice Climber (JE) - git mv _wip/Ice Climber (U) -> Ice Climber (U) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) - Updated _tools paths from ../../../_tools/ to ../../_tools/ Both projects: Pass 1/2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Ice Climber JE/U moved from _wip/ rows 23-24 to top-level rows 21-22 - Tier split: BYTE-IDENTICAL now rows 1-22 (+36), PRG-IDENTICAL rows 23-35 - Summary table: Added Ice Climber JE/U as rows 21-22 - _wip/ section: count 17 -> 15 - verify_pass1.py scope: 19 -> 22 top-level projects All 22 top-level projects verified byte-identical. Promote Lode Runner (U) to top-level: Pass 2/3 complete, whole-ROM byte-identical - git mv _wip/Lode Runner (U) -> Lode Runner (U) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Lode Runner moved from _wip/ row 25 to top-level row 23 - Tier split: BYTE-IDENTICAL now rows 1-23 (+36), PRG-IDENTICAL rows 24-35 - Summary table: Added Lode Runner as row 23 with BYTE-IDENTICAL (Pass 2/3 complete) - _wip/ section: count 15 -> 14 - verify_pass1.py scope: 22 -> 23 top-level projects - Audit comment count: 19 -> 22 top-level projects - Pass 3 symbols: 19 -> 22 top-level projects All 23 top-level projects verified byte-identical. TODO.md updated: - Current state: 22 -> 23 top-level projects - _wip/ count: 15 -> 14 - verify_pass1.py scope: 22 -> 23 - Audit comment count: 19 -> 22 - Pass 3 symbols: 19 -> 22 - Label residue table: Ice Climber JE/U removed, Lode Runner added - Five false Pass 2 claims section updated with Ice Climber & Lode Runner completions Promote Pinball (JU) to top-level: Pass 2/3 complete, whole-ROM byte-identical - git mv _wip/Pinball (JU) -> Pinball (JU) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Pinball moved from _wip/ row 30 to top-level row 24 - Tier split: BYTE-IDENTICAL now rows 1-24 (+36), PRG-IDENTICAL rows 25-35 - Summary table: Added Pinball as row 24 with BYTE-IDENTICAL (Pass 2/3 complete) - _wip/ section: count 13, rows 25-35 - verify_pass1.py scope: 23 -> 24 top-level projects - Audit comment count: 19 -> 22 top-level projects - Pass 3 symbols: 19 -> 22 top-level projects All 24 top-level projects verified byte-identical. TODO.md updated: - Current state: 23 -> 24 top-level projects - _wip/ count: 14 -> 13 - verify_pass1.py scope: 23 -> 24 - Label residue table updated - Documentation follow-ups updated Promote Millipede (U) and Popeye (JU) (PRG 0) to top-level Promote two _wip/ projects out of _wip/ into top-level folders, each now verified whole-ROM byte-identical against the original ROM. - Millipede (U): moves all copilot files up, adds header.s, chr.s, linker.cfg with PRG+HEADER+CHR areas, rom.nes copy, and switches the Makefile from PRG-only cmp to whole-ROM cmp. Makefile _tools depth adjusted (../../../../_tools/ -> ../../_tools/). - Popeye (JU) (PRG 0): same promotion treatment. - AGENTS.md: update layout notes (19 top-level projects mention the canonical audit format; update tier counts 27->29 top-level / 9->7 _wip). - OVERVIEW.md: root inventory table updated with the two promotions and re-numbered _wip rows. - TODO.md: handoff updates. - Remaining _wip projects: assorted pass 2/3 edits (prg.s, locals.inc, reconstructed.lbl, reconstructed_prg.bin) for Popeye (JU) (PRG 1), Pyramid (U), Raid on Bungeling Bay (U), Raid on Bungeling Bay (U) [a2].
12 KiB
Ice Climber (JE) — Pass 3 Complete (2026-10-19)
ROM: _wip/Ice Climber (JE)/Ice Climber (JE).nes (24,592 bytes, iNES). Status: Pass 1 complete — 74.6% code; Pass 2 complete; Pass 3 complete. copilot/prg.s is a real disassembly: 6,112 instructions (12,230 bytes as code), 973 label definitions all named — 76 semantic routine names + 212 SubC<addr> entry points + 685 <Routine>_Block[_n] internal branch targets. locals.inc holds 157 zero-page slots with 9 semantic aliases added. It builds byte-identical at the PRG level (cmp reconstructed_prg.bin prg.bin).
Pair note: the PRG and iNES header of this dump are byte-identical to Ice Climber (U); only the CHR pattern-table data differs (region-specific glyphs). The two prg.s files are now identical after Pass 3 completion.
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 completed 2026-10-19 — all hardware register operands and zero-page semantic aliases substituted via _tools/apply_symbols.py; multi-alias residues resolved via sites.tsv; data tables/structures named.
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 | Horizontal |
| Battery-backed flag | clear |
| ROM SHA-256 | d230a7febf7e639760b6088c2b38cf70686ed64d66974ad94fd5fcd5c0a3f50f |
| PRG SHA-256 | 6913935b8d55ade070555416d330472839f4cefbf865c15c8248796d2bc5cf29 |
Interrupt Vectors (taken from PRG $3FFA–$3FFF)
| Vector | Target | Note |
|---|---|---|
| NMI | $C076 |
per-frame heartbeat |
| RESET | $C014 |
enters at $C014 inside the first pages (the Donkey Kong Jr.-style non-$C000 entry) |
| IRQ | $FFF0 |
lands in the vector-page padding — unused placeholder |
copilot/ Contents
| File | Purpose |
|---|---|
Ice Climber (JE).nes |
original ROM image (reference) |
prg.bin / header.bin / chr.bin |
split image, produced by _tools/splitnes.py |
prg.s |
Pass 1/2/3: classified code/data split, 6,112 instructions, 973 labels all named (76 semantic + 212 SubC<addr> + 685 _Block), canonical audit comments; Pass 3 symbols applied |
symbols.inc |
shared NES hardware constants (PPU/APU/controller) |
locals.inc |
157 zero-page slots, 9 semantic aliases (e.g., PtrLo/PtrHi, Ptr2Lo, VBlankTimer, CheckZp14, SprData, SprData2, FrameCounter) |
sites.tsv |
per-line multi-alias resolutions for _tools/apply_symbols.py (3 entries) |
iceclimber_label_map.tsv / iceclimber_full_map.tsv |
Pass 2 draft maps — stale, see the audit below; kept for provenance only |
linker.cfg |
PRG at $C000, 16 KB, fill = yes |
Makefile |
links reconstructed_prg.bin and cmps it against prg.bin |
reconstructed_prg.bin |
build output from the last verified build |
Header/CHR are not linked: copilot/ keeps the raw header.bin/chr.bin splits and make verifies PRG bytes only, so the header and CHR are not proven for this project. Promotion to the top level adds header.s / chr.s (as e.g. Burger Time (U)), a HEADER/PRG/CHR linker.cfg, and a full-ROM make target.
Build
cd "_wip/Ice Climber (JE)/copilot"
make # → cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
Pass Status
| Pass | Status |
|---|---|
| Pass 1 | Complete — 6,112 instructions, 12,230 bytes as code (74.6%), 4,154 bytes as data, byte accounting exact (16,384) |
| Pass 2 | Complete — 973 label definitions all named (76 semantic + 212 SubC<addr> + 685 _Block); 157 zero-page slots with 9 semantic aliases in locals.inc |
| Pass 3 | Complete — 91 operands substituted via _tools/apply_symbols.py (23 single-name + 68 hardware); 3 multi-alias residues resolved via sites.tsv; data tables/structures named |
| Promotion | Outstanding — add header.s/chr.s + HEADER/PRG/CHR linker.cfg, switch make to a whole-ROM cmp, move the _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:
- "237 semantic labels" was the number of distinct old names in the map (
iceclimber_full_map.tsvhas 255labelrows covering 237 distinctLabel_names).prg.sactually named 76 definitions; the other 897 were stillLabel_. - "157 zero-page semantic aliases" was the count of neutral
ZpXXentries inlocals.inc. There were zero semantic zero-page aliases. - Most of the listed "key renamed routines" (
InitSystem,InitPPU,InitGameState,InitSprites,UpdateOAM,CheckController,CheckCollision,WritePPU, …) did not exist in the file. - The instruction count/percentage were also off by one instruction and a rounding step: the file holds 6,112 instructions totalling 12,230 bytes (74.6%), not 6,113 / 74.7%.
Draft-map audit (iceclimber_full_map.tsv, 255 label rows):
| Outcome | Rows | Meaning |
|---|---|---|
| already applied | 76 | the proposed name already sits at that address |
| name conflict | 13 | a different name already sits there |
| stale address | 96 | the address is not an instruction boundary in the current prg.s — the map was built against a different code/data split |
| unlabelled, unreferenced | 70 | an instruction boundary with no label and no operand referencing it |
| safely applicable | 0 | no row is both insertable and corroborated by control flow |
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)
-
76 semantic routine names preserved from Pass 1:
Reset,WaitVBlank_1,WaitVBlank2_1,ClearZeropage,InitAPU_1,InitAPU_3,MainGameLoop,GameLoopEntry_1,NmiHandler,NmiFrameUpdate,NmiFrameUpdate2,GameStateUpdate,UpdateGameState3_1,UpdateGameState4,UpdateGameState5,UpdateGameState6,GameLoopEntry_4,UpdateGameState2_4,UpdateGameState2_5,WaitVBlankFrame2,InitSprites_1,CopySprites,UpdateSprites_1,UpdateSprites2_1,UpdateSprites2_3,CheckZp34,UpdateSprites2_4,UpdateSprites3_1,InitSprites_3,UpdateSprites_3,UpdateSprites2_5,UpdateSprites3_3,CheckZp14,UpdateSprites2_7,UpdateSprites3_5,UpdateSprites4_1,UpdateSprites4_3,UpdateSprites4_4,GameLoopEntry_5,InitGameState,UpdateSprites2_8,UpdateSprites2_9,InitSprites2_1,ClearSpriteRAM,UpdateSprites2_10,UpdateSprites2_11,UpdateSprites3_6,UpdateSprites4_5,UpdateSprites5_1,UpdateSprites6,UpdateSprites7_1,UpdateSprites7_3,UpdateSprites8,UpdateSprites9,UpdateSprites10,UpdateSprites11,UpdateSprites11,UpdateSprites12,UpdateSprites13,InitSprites_4,InitSprites3,UpdateController,UpdateController2_4,UpdateSprites_4,UpdateSprites2_12,UpdateSprites4_7,UpdateSprites4_6,UpdateSprites5_4,UpdateSound4_11,UpdateSound6,UpdateSound12_4,UpdateSound24_1,UpdateSound26_1,UpdateSound26_11,UpdateSound26_13,UpdateMusicChannel19. -
212
SubC<addr>entry points for all remainingjsr/jmptargets that lacked semantic names (e.g.,SubCC166at $C166,SubCC1D7at $C1D7, etc.). -
685
<Routine>_Block[_n]internal branch targets derived from control flow within each routine (e.g.,Reset_Block,MainGameLoop_Block…MainGameLoop_Block_5,SubCC166_Block…SubCC166_Block_5, etc.).
Zero-Page Semantic Aliases (9, added 2026-10-19)
| Alias | ZpXX | Address | Role |
|---|---|---|---|
PtrLo |
Zp00 |
$00 | general-purpose indirect pointer low byte (sta (Zp00),y) |
PtrHi |
Zp01 |
$01 | general-purpose indirect pointer high byte |
Ptr2Lo |
Zp02 |
$02 | secondary indirect pointer low byte |
VBlankTimer |
Zp10 |
$10 | vblank wait counter / frame timer |
CheckZp14 |
Zp55 |
$55 | button/input state (CheckZp14 routine) |
SprData |
Zp5A |
$5A | sprite data pointer (SubCC38D, SubCCD13, etc.) |
SprData2 |
Zp5F |
$5F | secondary sprite data pointer |
FrameCounter |
ZpF3 |
$F3 | frame counter |
Hardware Register Substitution (68 operands, 2026-10-19)
_tools/apply_symbols.py substituted 68 hardware register operands (PPU, APU, controller) in prg.s. Build remains BYTE-IDENTICAL.
Pass 3 Symbol Application (2026-10-19)
_tools/apply_symbols.py substituted 91 operands total:
- 23 single-name operands (zero-page semantic aliases)
- 68 hardware register operands (PPU, APU, controller)
3 multi-alias residues resolved via sites.tsv:
| Line | Operand | Chosen Alias |
|---|---|---|
| 706 | a:$005F,y |
SprData2 |
| 1612 | ($0002) |
Ptr2Lo |
| 11140 | a:$0000,y |
PtrLo |
Data Tables / Structures Named (Pass 3, 2026-10-19)
| Table | Address | Description |
|---|---|---|
InitialSpriteData |
$C000 | 20-byte initial sprite data for CopySprites |
LevelLayoutData |
$E382 | Level layout / enemy placement data |
LevelDataPtrs |
$DEF6 | Pointers to level data blocks |
SpriteDataTable1 |
$C440 | Sprite animation/init table 1 |
SpriteDataTable2 |
$CBB3 | Sprite animation/init table 2 |
SpriteDataTable3 |
$CC67 | Sprite animation/init table 3 |
SpriteDataTable4 |
$D75E | Sprite animation/init table 4 |
SpriteAnimTable1 |
$C2ED | Sprite animation sequence table 1 |
SpriteAnimTable2 |
$C3A4 | Sprite animation sequence table 2 |
SpriteAnimTable3 |
$CD20 | Sprite animation sequence table 3 |
SpriteInitTable |
$C512 | Sprite initialization parameter table |
CheckZp14_Data |
$C680 | Data for CheckZp14 routine |
EnemySpriteData |
$E88C | Enemy sprite definitions |
EnemyAnimData1 |
$E9CE | Enemy animation data 1 |
EnemyAnimData2 |
$EA5C | Enemy animation data 2 |
EnemyAnimData3 |
$EAA2 | Enemy animation data 3 |
MusicSequenceData |
$F1AC | Music sequence data |
SoundEffectTable1 |
$F149 | Sound effect table 1 |
MusicChannelData |
$F466 | Music channel data 1 |
MusicChannelData2 |
$F5C9 | Music channel data 2 |
MusicChannelData3 |
$F67E | Music channel data 3 |
MusicChannelData4 |
$F6EF | Music channel data 4 |
MusicChannelData5 |
$F7B8 | Music channel data 5 |
SoundEngineData |
$FB3B | Sound engine parameter data |
VectorTableAndPadding |
$FD22 | IRQ/NMI/RESET vectors + padding |
Power-On Flow Analysis
Reset ($C014) → sei/cld → PPU init → stack init (ldx #$FF/txs) → WaitVBlank_1/WaitVBlank2_1 (poll PPUSTATUS for vblank) → ClearZeropage (clear $0000–$01FF via (Zp00),y) → InitAPU_1/InitAPU_3 → MainGameLoop / GameLoopEntry_1 → NMI synchronization → game state dispatch.
NMI ($C076) → saves registers → PPU status read → sprite DMA via OAMDMA → scroll update (PPUSCROLL) → PPU mask/ctrl updates → GameStateUpdate dispatch → restores registers → rti.
IRQ ($FFF0) → rti immediately (unused placeholder in vector-page padding).
Main Game Loop → MainGameLoop / GameLoopEntry_1 → calls GameStateUpdate → NmiHandler synchronization → loops.
Promotion Checklist (Outstanding)
- Add
header.s/chr.s(one-line.incbineach) - Add HEADER/PRG/CHR
linker.cfg(seeBurger Time (U)/copilot/) - Add
copilot/rom.nessymlink to original ROM - Switch
Makefileto whole-ROMcmp reconstructed.nes rom.nes - Move
_toolspaths from../../../_tools/to../../_tools/ - Verify
makeprintsByte-identical ROM verified. git mv "_wip/Ice Climber (JE)" "Ice Climber (JE)"and update rootOVERVIEW.mdinventory