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

117 lines
9.2 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.
# Ice Climber (JE) — Pass 2 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.** `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 2 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.
## 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: classified code/data split, 6,112 instructions, **973 labels all named** (76 semantic + 212 `SubC<addr>` + 685 `_Block`), canonical audit comments |
| `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`) |
| `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 `cmp`s 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
```bash
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 | **Partial** — hardware register operands substituted (68); zero-page semantic aliases defined in `locals.inc` but multi-alias addresses need `--sites` file for full substitution; data tables/structures unnamed |
| 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.tsv` has 255 `label` rows covering 237 distinct `Label_` names). `prg.s` actually named **76** definitions; the other 897 were still `Label_`.
- **"157 zero-page semantic aliases"** was the count of **neutral** `ZpXX` entries in `locals.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 remaining `jsr`/`jmp` targets that lacked semantic names (e.g., `SubCC166` at $C166, `SubCC1D7` at $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` | $FF | 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**.
## 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.
**Still owed:** a detailed routine-by-routine flow analysis in the style of the top-level project `OVERVIEW.md`s.