Files
disasm/_wip/Mario Bros (JU)/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

293 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.
# Mario Bros (JU) — Game Flow from Power-On
This document describes the execution flow of the disassembled Mario Bros (JU) 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–$C06A)
On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization:
| Step | Code | Purpose |
|------|------|---------|
| 1–2 | `cld` / `sei` | Clear decimal mode, disable interrupts |
| 3–6 | `WaitVblank1` ($C002): poll `$2002` via `bpl *-3` | Wait for first VBlank (PPU warm-up) |
| 7–8 | `ldx #$00` / `txs` | Initialize stack pointer to $01FF |
| 9–10 | `ldx Zp70` / `ldy #$06` / `sty Zp01` / `ldy #$00` / `sty Zp00` | Zp00/Zp01 = $0600 (RAM clear pointer); X = Zp70 ($70) |
| 11–15 | `ClearRamPageLoop` ($C01D): clear RAM via (Zp00),Y | Clear 6 pages ($0000–$05FF); dec Zp01 loops pages |
| 16 | `txa` / `bne Label_C02B` | If Zp70 was non-zero, skip full clear |
| 17 | `ldx #$5F` | Alternate init value for warm start |
| 18 | `stx $0500` | Store signature at $0500 |
| 19 | `jsr SubCCA1B` / `jsr SubCCA2B` | Initialize game systems |
| 20 | `ldy #$00` / `sta $2005` ×2 | PPUSCROLL = 0,0 |
| 21 | `iny` / `sty Zp30` | Zp30 ($30) = 1 |
| 22 | `lda #$0F` / `sta $4015` | Enable all APU channels |
| 23 | `lda #$90` / `sta $2000` / `sta Zp09` | PPUCTRL: NMI on, sprite $0000, BG $1000 + shadow in Zp09 |
| 24 | `lda #$06` / `sta Zp0A` | Zp0A ($0A) = $06 |
**Warm-start signature:** Byte at `$0500` survives RAM clear via Zp70 ($70). On soft reset, if non-zero, the game clears only 6 pages instead of 7 and uses X=$5F.
---
## 2. Post-Reset Initialization — `SubCC04F` ($C04F) onward
```
SubCC04F ($C04F):
lda #$00 / sta Zp20 ($20)
lda Zp30 ($30)
beq Label_C05D ; if 0: jsr SubCF8A7
lda Zp50 ($50)
cmp #$01 / bne Label_C060 ; if 1: jsr SubCF8A7
Label_C060:
jsr SubCCD88 ; title/init
jsr SubCC4B8 ; gameplay init
lda #$01 / sta Zp22 ($22) ; NMI phase flag = 1 (gameplay)
```
Then falls through to main loop entry.
---
## 3. Interrupt Vectors — CPU $FFFA–$FFFF
Located at PRG offset $3FFA (end of 16 KB bank):
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C07D | `NmiHandler` |
| RESET | $C000 | `Reset` |
| IRQ | $C000 | `Reset` (reused — unused placeholder) |
---
## 4. NMI / Frame Heartbeat — `NmiHandler` ($C07D)
The NMI fires once per frame and implements a two-phase structure controlled by **Zp22** ($22):
```
NmiHandler ($C07D):
pha / txa / pha / tya / pha ; preserve A,X,Y
lda #$00 / sta $2003 (OAMADDR)
lda #$02 / sta $4014 (OAMDMA) ; sprite DMA from $0200
lda Zp22 ($22)
beq Label_C0AC ; phase 0: minimal
jsr SubCCB58 ; gameplay update 1
jsr SubCEE6A ; gameplay update 2
jsr SubCCCFF ; gameplay update 3
jsr SubCCA66 ; gameplay update 4
jsr SubCCE09 ; gameplay update 5
jsr SubCCCC5 ; gameplay update 6
jsr SubCCAF7 ; gameplay update 7
ldy #$01 / sty Zp20 ($20) ; main loop flag = 1
dey / sty Zp42 ($42) ; Zp42 = 0
Label_C0AC:
lda #$01 / sta Zp23 ($23) ; NMI done flag
pla / tay / pla / tax / pla / rti ; restore, return
```
**Phase 0 (Zp22=0):** Title/attract — only OAM DMA, no game logic.
**Phase 1 (Zp22=1):** Gameplay — 7 gameplay update calls, sets main loop flag Zp20=1.
---
## 5. NMI Phase Dispatcher — `NmiPhaseDispatch` ($C0B6)
Called from main loop to synchronize with NMI:
```
NmiPhaseDispatch ($C0B6):
lda ZpB0 ($B0)
beq Label_C0BF ; if 0: jmp NmiPhaseHandlerA
lda $0320 / bne Label_C0C2 ; check $0320
Label_C0BF:
jmp NmiPhaseHandlerA ; $C149
Label_C0C2:
lda #$20 / sta Zp14 ($14)
lda #$03 / sta Zp15 ($15)
lda #$01 / sta Zp11 ($11)
ldy #$0B / ldx #$08 / jsr SubCC44A
sta $05F7 / ora #$00 / beq NmiPhaseHandlerA
lda ZpC6 / ora $0336 / beq Label_C0F3
...
```
**ZpB0** ($B0) = NMI phase (0=title, non-zero=gameplay).
**$0320** = Additional state check.
---
## 6. Main Game Loop
The main loop runs in the main thread, synchronized with NMI via Zp20/Zp22/Zp23:
```
Main Loop (entry via SubCC06A / SubCC06D area):
; Wait for NMI to set Zp20=1 (frame complete)
; Process game logic
; Clear Zp22 (NMI phase) for next frame
; Loop
```
Key synchronization points:
- **Zp20** ($20): Set to 1 by NMI phase 1; main loop waits for this
- **Zp22** ($22): NMI phase flag (set to 1 by reset init, cleared by main loop)
- **Zp23** ($23): NMI completion flag
---
## 7. Key Subsystems
### 7.1 Gameplay Update Chain (NMI Phase 1)
Seven sequential calls from NMI:
1. `SubCCB58` ($CB58) — Player/enemy physics
2. `SubCEE6A` ($EE6A) — Collision detection
3. `SubCCCFF` ($CCFF) — Platform/pipe logic
4. `SubCCA66` ($CA66) — Sprite animation
5. `SubCCE09` ($CE09) — Bonus/coin logic
6. `SubCCCC5` ($CCC5) — Score/HUD update
7. `SubCCAF7` ($CAF7) — Audio trigger
### 7.2 Audio Engine — `$F800+` region
- `SubCF8A7` ($F8A7): Audio init (called from reset)
- `SubCCD88` ($CD88): Title music
- `SubCC4B8` ($C4B8): Gameplay music
- APU registers at $4000–$4013, frame counter at $4017
### 7.3 Stack-Based Dispatch — `$CDB1` (`jmp ($0014)`)
Indirect jump through ZP pointer at $0014. The target address is pushed on stack by callers — cannot be followed statically. This is the main dispatch mechanism for 27.8% of unreached code.
### 7.4 JSR Thunk Table — `$C5A3`
20-entry table of `jsr`/`rts` pairs. Seeding this recovered major code blocks.
### 7.5 Controller Input
Read via `$4016` strobe/shift in NMI or main loop. Button state stored in ZP ($10–$15 area).
---
## 8. Memory Map (Key Zero Page / RAM)
| Address | Name | Use |
|---------|------|-----|
| $00–$01 | `Zp00`/`Zp01` | 16-bit pointer (RAM clear) |
| $09 | `Zp09` | PPUCTRL shadow ($90) |
| $0A | `Zp0A` | Init constant ($06) |
| $10 | `Zp10` | PPUCTRL working copy |
| $11 | `Zp11` | State flag |
| $14–$15 | `Zp14`/`Zp15` | NMI dispatch parameters |
| $20 | `Zp20` | Main loop sync flag (set by NMI) |
| $22 | `Zp22` | NMI phase (0=title, 1=gameplay) |
| $23 | `Zp23` | NMI done flag |
| $30 | `Zp30` | Reset signature check result |
| $42 | `Zp42` | Cleared by NMI phase 1 |
| $50 | `Zp50` | Game mode (0=title, 1=gameplay) |
| $70 | `Zp70` | Warm-start signature ($0500) |
| $B0 | `ZpB0` | NMI phase dispatch selector |
| $B1 | `ZpB1` | Heavily used (88 refs) — gameplay state |
| $B2–$BF | `ZpB2`–`ZpBF` | Object positions, velocities, timers |
| $C0–$CF | `ZpC0`–`ZpCF` | Enemy/bonus state arrays |
| $F0–$FF | `ZpF0`–`ZpFF` | Audio engine ZP |
| $0200 | OAM buffer | Sprite DMA source |
| $0300+ | Game state | Platforms, pipes, enemies, bonuses |
| $0500 | Warm-start signature | Must match Zp70 for warm start |
---
## 9. Data Tables
| Table | Address | Description |
|-------|---------|-------------|
| JSR Thunk Table | $C5A3 | 20 entries of `jsr target` / `rts` |
| Stack Dispatch Targets | (dynamic) | Pushed on stack, jumped via `jmp ($0014)` at $CDB1 |
| Audio Sequences | $F800+ | Music/sound data |
| Level Layout | $0300+ | Platform positions, pipe connections |
---
## 10. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C000) ────► cld/sei, wait VBlank, stack=$FF
│
├─ Check Zp70 ($70) warm-start signature at $0500
│ ├─ Zero: Clear 7 pages ($0000–$06FF), X=$00
│ └─ Non-zero: Clear 6 pages ($0000–$05FF), X=$5F
│
├─ stx $0500 (signature)
├─ jsr SubCCA1B, SubCCA2B (system init)
├─ PPUSCROLL = 0,0
├─ Zp30 = 1
├─ APU enable ($4015=$0F)
├─ PPUCTRL = $90 (NMI on) → Zp09
├─ Zp0A = $06
│
▼
SubCC04F ($C04F) ──► Zp20=0
│
├─ Zp30=0 or Zp50=1 → jsr SubCF8A7 (audio init)
│
└─ jsr SubCCD88, SubCC4B8 (title/gameplay init)
lda #$01 / sta Zp22 (NMI phase = gameplay)
│
▼ (fall through to main loop)
Main Loop ────────────────────────────────────────────────────┐
│ │
├─ Wait for Zp20=1 (set by NMI phase 1) │
├─ Process frame logic │
├─ Call NmiPhaseDispatch ($C0B6) │
│ ZpB0=0 → NmiPhaseHandlerA ($C149) │
│ ZpB0≠0 → check $0320, setup Zp14/15/11, jsr SubCC44A │
├─ Clear Zp22 (NMI phase) for next frame │
└─ Loop │
NMI ($C07D) [once per frame]
├─ Preserve A,X,Y
├─ OAM DMA ($4014 from $0200)
├─ lda Zp22
│ ├─ =0 (Title): Label_C0AC → set Zp23=1, rti
│ └─ =1 (Gameplay):
│ jsr SubCCB58 (physics)
│ jsr SubCEE6A (collision)
│ jsr SubCCCFF (platforms)
│ jsr SubCCA66 (sprites)
│ jsr SubCCE09 (bonus)
│ jsr SubCCCC5 (HUD)
│ jsr SubCCAF7 (audio trigger)
│ ldy #$01 / sty Zp20 (signal main loop)
│ dey / sty Zp42 (=0)
├─ lda #$01 / sta Zp23 (NMI done)
└─ Restore A,X,Y, rti
```
---
## 11. Naming and Residue
**Pass 1 complete** — 5,735 instructions / 11,829 code bytes (72.2%), byte accounting exact. 937 labels, 937 routine headers. Reached via:
- Vectors + direct `jsr`/`jmp`/branch, official opcodes: 1,185 instr (14.6%)
- `--undocumented` (105 extra NMOS opcodes): 1,210 instr (14.9%)
- `--auto-seed` (61 table-referenced entries): 4,187 instr (51.1%)
- `--extra-seed 0xC0B6,0xC5A3`: 5,735 instr (72.2%)
**Pass 2 substantial** — **262 call entry points named** (verified 2026-10-19):
- **39 semantic names**: Audio routines in `$F8xx-$F9xx`, gameplay helpers (`CheckZpB3Return`, `CheckRangeReturn012`, `ToggleZpFC_Bit3`)
- **223 `SubC<addr>` address-based names** for remaining `jsr`/`jmp` targets
- **667 `Label_Cxxxx` internal branch targets remain** — local control-flow targets within routines
- Zero raw `$XXXX` call operands remain (all 478 `jsr`/`jmp` sites resolved to labels)
**Pass 3 outstanding** — Data regions in 4,555 unreached bytes need naming; 151 neutral `ZpXX` aliases in `locals.inc`.
**Indirect dispatch** (not followed statically): `jmp ($0014)` at $CDB1 — table pointer arrives via `pla`, only knowable in caller's frame.
**Tracer fixes required** (in `_tools/nesdis.py`):
- Control-flow targets win over fall-through (fixed `bne` at $C4BA targeting $C528 vs fall-through `NOP #$A5` at $C527)
- Branch targets inside surviving instruction must be pruned (otherwise `Label_C528` undefined)
**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).