Files
disasm/Lode Runner (U)/OVERVIEW.md
T
tmerritt 9eb04ea7ef Adds overview for WIP projects
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].
2026-10-19 19:58:41 -04:00

272 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.
# Lode Runner (U) — Game Flow from Power-On
This document describes the execution flow of the disassembled Lode Runner (U) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `copilot/prg.s` in the repository. The rebuild is verified **byte-identical** to the original `prg.bin` at the PRG level.
---
## 1. Reset Vector — CPU $FFFC → $C000
**`Reset`** ($C000–$C029)
On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization:
| Step | Code | Purpose |
|------|------|---------|
| 1–2 | `sei` / `cld` | Disable interrupts, clear decimal mode |
| 3–4 | `ldx #$FF` / `bne Label_C00A` | X=$FF, branch always taken (delay slot?) |
| 5–6 | `txs` / `jmp $C012` | Initialize stack pointer to $01FF |
| 7–8 | `ldy #$00` / `sty Zp00` / `sty Zp01` | Zp00/Zp01 = $0000 (RAM clear pointer) |
| 9 | `tya` / `ldx #$08` | A=0, X=8 (8 pages to clear) |
| 10–14 | `Label_C01B`: `sta (Zp00),y` / `iny` / `bne` / `inc Zp01` / `dex` / `bne` | Clear $0000–$07FF (8 pages of 256 bytes) |
| 15 | `lda #$23` / `sta ZpE5` | ZpE5 ($E5) = $23 |
| 16 | `jmp $C3A6` | Jump to main initialization |
**No warm-start signature check** — full 2 KB clear every reset.
---
## 2. Interrupt Vectors — CPU $FFFA–$FFFF
Located at PRG offset $3FFA (end of 16 KB bank):
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C02C | `NmiHandler` |
| RESET | $C000 | `Reset` |
| IRQ | $FFF9 | `IrqHandler` (single `rti` — unused) |
---
## 3. NMI / Frame Heartbeat — `NmiHandler` ($C02C)
The NMI fires once per frame and implements a two-path structure controlled by **Zp10** ($10):
```
NmiHandler ($C02C):
pha / txa / pha / tya / pha ; preserve A,X,Y
jsr $C27C ; common NMI setup
lda Zp10 ($10)
beq Label_C050 ; Zp10=0: Path A
lda #$00 / sta Zp10 ; clear Zp10
lda Zp11 ($11) / tax
beq Label_C043 ; Zp11=0: Path B
bne Label_C050 ; Zp11≠0: Path A
Label_C043:
jsr $C18E ; Path B: PPU/game update
lda #$20 / ldx #$00 / jsr $C178 ; PPU write ($2000 + X=0)
jmp $C0A4 ; → main NMI exit
Label_C050: ; Path A: Palette/sprite update
lda #$3F / ldx #$08 / jsr $C178 ; PPUADDR=$3F08 (palette)
inc Zp9E ($9E) ; frame sub-counter
lda Zp9E / and #$1F / bne Label_C07B ; every 32 frames
inc Zp9F ($9F) ; secondary counter
lda Zp9F / and #$03 / tax ; 0-3 selector
lda #$0F / jsr $C006 ; PPUDATA write
lda $C39E,x / jsr $C006 ; palette data from table
lda #$00 / jsr $C006 ; clear
lda #$30 / jsr $C006 ; $30
Label_C07B:
lda Zp9E / and #$07 / bne Label_C0A4 ; every 8 frames
lda #$3F / ldx #$0C / jsr $C178 ; PPUADDR=$3F0C
inc Zp72 ($72) ; tertiary counter
lda Zp72 / and #$03 / tax ; 0-3 selector
lda #$0F / jsr $C006
lda #$38 / jsr $C006
lda #$28 / jsr $C006
lda $C3A2,x / jsr $C006 ; sprite palette data
Label_C0A4: ; Common NMI exit
lda Zp03 ($03) / sta $2001 (PPUMASK) ; apply PPUMASK
jsr $C1E1 ; sprite/OAM update
lda ZpDB ($DB)
beq Label_C0D1 ; if 0: skip gameplay
lda Zp06 ($06) / and #$10 / beq Label_C0D1 ; bit 4 check
jsr $C2C6 / jsr $DE9F / jsr $C1D7 ; gameplay logic
jmp Label_C127 ; rti
Label_C0BF: ; Alternate entry (ZpDB=0?)
jsr $C1E1
lda Zp06 / and #$10 / beq Label_C0BF ; wait for bit 4
jsr $DE9F / jsr $C1D7 / jsr $C2B3
jmp Label_C127
Label_C0D1:
lda ZpB7 ($B7)
beq Label_C127 ; if 0: rti
jsr $E271 ; level/game init
inc ZpB9 / lda ZpB9 / cmp ZpB8 / bcc Label_C127
lda #$00 / sta ZpB9 ; reset counter
... (level transition logic)
Label_C127:
pla / tay / pla / tax / pla / rti ; restore, return
```
**Two NMI Modes:**
- **Path A (Zp10≠0):** Palette/sprite updates, periodic palette cycling (every 8/32 frames), sprite DMA
- **Path B (Zp10=0, Zp11=0):** `jsr $C18E` → PPU register update → main exit
**Key ZP Flags:**
- `Zp10` ($10): NMI path selector (0=Path B, non-zero=Path A)
- `Zp11` ($11): Sub-path selector for Path B
- `Zp9E` ($9E): Frame sub-counter (palette cycling)
- `Zp9F` ($9F): Secondary frame counter
- `Zp72` ($72): Tertiary counter (sprite palette)
- `ZpDB` ($DB): Gameplay active flag
- `Zp06` ($06): Bit 4 = gameplay trigger
- `ZpB7` ($B7): Level transition flag
- `Zp03` ($03): PPUMASK shadow
---
## 4. Main Initialization — `$C3A6` (from Reset)
After reset jumps to `$C3A6`, the game initializes title screen and enters main loop. The main loop is driven by NMI synchronization — the main thread waits for NMI to signal frame completion via ZP flags.
Key initialization routines (reached from `$C3A6`):
- `$C178`: PPU address/data write helper (A=addr high, X=addr low → $2006/$2006/$2007)
- `$C18E`: PPU control update (reads Zp03→PPUMASK, Zp10→PPUCTRL)
- `$C1E1`: Sprite/OAM update (builds OAM at $0200)
- `$C27C`: Common NMI setup (called at NMI entry)
---
## 5. Key Subsystems
### 5.1 PPU Helper — `$C178` / `$C006`
- `$C178`: Set PPUADDR from A (high) / X (low), write via `$2006` twice
- `$C006`: Write A to PPUDATA ($2007), RTS
- `$C18E`: Read PPUSTATUS, write Zp03→PPUMASK, Zp10→PPUCTRL
### 5.2 Sprite/OAM System — `$C1E1` / `$C1D7`
- Builds OAM at $0200 from object state arrays
- `$C1D7`: Called from NMI paths for per-frame sprite update
### 5.3 Gameplay Logic — `$C2C6`, `$DE9F`, `$C1D7`, `$C2B3`
- `$C2C6`: Player/input handling
- `$DE9F`: Enemy/guard AI
- `$C2B3`: Level progression, scoring
### 5.4 Level Data & Transitions — `$E271`, `$C0E4`–`$C123`
- `$E271`: Level load/init
- `$C0E4`: State machine on `ZpBE`/`ZpBA` (pointer to level data)
- Level data tables at `$C2F0`, `$C378`, `$C39E`, `$C3A2`
### 5.5 Audio — `$C12D` / `$C143` / `$C16E`
- APU register writes ($4000–$4003) for pulse channels
- Frequency data from `$C2F0`, `$C378`
- Called from level transition / gameplay
---
## 6. Memory Map (Key Zero Page / RAM)
| Address | Name | Use |
|---------|------|-----|
| $00–$01 | `Zp00`/`Zp01` | 16-bit pointer (RAM clear, PPU) |
| $03 | `Zp03` | PPUMASK shadow |
| $06 | `Zp06` | Gameplay flags (bit 4 = trigger) |
| $10 | `Zp10` | NMI path selector (0=title, ≠0=game) |
| $11 | `Zp11` | NMI sub-path selector |
| $12–$13 | `Zp12`/`Zp13` | Object/sprite pointers |
| $20–$21 | `Zp20`/`Zp21` | Player position (X/Y) |
| $26 | `Zp26` | Player state/animation |
| $28 | `Zp28` | Player velocity |
| $32–$35 | `Zp32`–`Zp35` | Enemy/guard positions |
| $39 | `Zp39` | Level timer |
| $52–$5F | `Zp52`–`Zp5F` | Sprite attribute buffers |
| $72 | `Zp72` | Sprite palette cycle counter |
| $85–$86 | `Zp85`/`Zp86` | Level data pointers |
| $9E–$9F | `Zp9E`/`Zp9F` | Frame counters (palette cycling) |
| $B7 | `ZpB7` | Level transition trigger |
| $B8–$B9 | `ZpB8`/`ZpB9` | Level transition counters |
| $BA–$BF | `ZpBA`–`ZpBF` | Level data pointer + state |
| $DB | `ZpDB` | Gameplay active flag |
| $E5 | `ZpE5` | Init constant ($23) |
| $0200 | OAM buffer | 64 sprites (DMA source) |
| $0300+ | Game state arrays | Player, enemies, level data |
---
## 7. Data Tables
| Table | Address | Description |
|-------|---------|-------------|
| Palette data (BG) | $C39E | 4 entries, indexed by Zp9F&3 |
| Palette data (sprites) | $C3A2 | 4 entries, indexed by Zp72&3 |
| Audio frequency low | $C2F0 | Note→period low byte |
| Audio frequency high | $C378 | Note→period high byte (×2 + index) |
| Level data pointer | `ZpBA`/`ZpBC` | Indirect pointer to level scripts |
---
## 8. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C000) ────► sei/cld, stack=$FF
│
├─ Clear $0000–$07FF (8 pages via Zp00/Zp01)
│
├─ ZpE5 = $23
│
▼
jmp $C3A6 (Main Initialization)
│
├─ Title screen setup
├─ PPU/APU init
└─ Enter main loop (VBlank synchronized)
Main Loop (implicit, VBlank-driven)
│
▼ (NMI fires once per frame)
NMI ($C02C)
├─ Preserve A,X,Y
├─ jsr $C27C (common setup)
├─ lda Zp10
│ ├─ =0 (Title/Menu):
│ │ lda Zp11
│ │ ├─ =0: jsr $C18E (PPU update), $C178, jmp $C0A4
│ │ └─ ≠0: Path A (palette cycling)
│ │
│ └─ ≠0 (Gameplay):
│ lda #$00 / sta Zp10
│ lda Zp11 / tax
│ ├─ =0: jsr $C18E, $C178, jmp $C0A4
│ └─ ≠0: Path A (palette cycling)
│
├─ Path A (Label_C050):
│ Palette writes to $3F08/$3F0C every 8/32 frames
│ inc Zp9E, Zp9F, Zp72
│
├─ Common Exit ($C0A4):
│ lda Zp03 → PPUMASK
│ jsr $C1E1 (sprite update)
│ lda ZpDB
│ ├─ =0: Label_C0BF (wait for Zp06 bit 4)
│ └─ ≠0: Check Zp06 bit 4
│ ├─ Clear: Label_C0D1 (level transition check)
│ └─ Set: jsr $C2C6, $DE9F, $C1D7 (gameplay)
│
└─ Label_C127: Restore A,X,Y, rti
Gameplay (when ZpDB≠0, Zp06&$10)
├─ $C2C6: Player control
├─ $DE9F: Enemy AI
├─ $C1D7: Sprite update
├─ $C2B3: Level/scoring
└─ Level transition: $E271, $C0E4 state machine
```
---
## 9. Naming and Residue
**Pass 1 complete** — 11,057 lines, 627 `Label_` definitions, canonical audit comments, 52.6% code.
**Pass 2 complete (2026-10-20)** — All 627 label definitions renamed. 196 semantic routine entry points named via `_tools/pass2.py` derive/apply using a hand-curated routinemap; remaining 431 internal branch targets named as `<Routine>_Block[_n]`. Zero `Label_Cxxxx` definitions remain.
**Pass 3 partial (2026-10-20)** — `_tools/apply_symbols.py` substituted **85 operands** (83 hardware registers + 2 single-name zero-page operands). Zero multi-alias residue. **2 semantic zero-page aliases applied**; remaining 150 zero-page slots retain neutral `ZpXX` names in `locals.inc`. Data tables/structures named in Memory Map section.
**Promotion outstanding** — Add `header.s`/`chr.s` + HEADER/PRG/CHR `linker.cfg`, switch `make` to whole-ROM `cmp`, move `_tools` paths from `../../../_tools/` to `../../_tools/`.
Build verification: `make` in `copilot/` produces `reconstructed_prg.bin`, compared against `prg.bin` (PRG byte-identical).