Files
disasm/Millipede (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

266 lines
10 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.
# Millipede (U) — Game Flow from Power-On
This document describes the execution flow of the disassembled Millipede (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 → $C735
**`Reset`** ($C735–$C7B3)
On power-on or reset, the 6502 jumps to **$C735** (non-$C000 entry, Donkey Kong Jr. style). The reset routine performs cold-start hardware initialization:
| Step | Code | Purpose |
|------|------|---------|
| 1–2 | `sei` / `cld` | Disable interrupts, clear decimal mode |
| 3–4 | `lda #$00` / `sta $2000` | PPUCTRL = 0 (NMI off, sprite $0000, BG $0000) |
| 5–6 | `ldx #$FF` / `txs` | Initialize stack pointer to $01FF |
| 7–11 | `Label_C73F`: poll `$2002` via `and #$80` / `beq` | Wait for VBlank (bit 7 of PPUSTATUS) |
| 12–13 | `ldy #$06` / `sty Zp01` / `ldy #$00` / `sty Zp00` | Zp00/Zp01 = $0600 (RAM clear pointer) |
| 14 | `tya` | A = 0 |
| 15–19 | `Label_C74F`: `sta (Zp00),y` / `dey` / `bne` / `dec Zp01` / `bpl` | Clear $0000–$05FF (6 pages) |
| 20 | `jsr $C76A` | Initialize object state at $03D0–$03F8 |
| 21 | `lda Zp10` / `ora #$80` / `sta $2000` / `sta Zp10` | PPUCTRL |= $80 (NMI on) + shadow in Zp10 |
| 22 | `Label_C764`: `jsr $F09B` / `jmp $C764` | Infinite loop: audio init? (waits for NMI) |
**`$C76A` (Label_C76A)** — Object initialization:
```
ldx #$29 / lda #$24
Label_C76E:
sta $03D0,x ; clear $03D0–$03F9 (42 bytes)
dex / bne Label_C76E
ldx #$00
Label_C776:
lda $C271,x / sta $03DF,x ; copy 6 bytes from $C271 to $03DF
inx / cpx #$06 / bne Label_C776
lda #$2C / sta $03D0
lda #$2D / sta $03E6
lda #$24 / sta $03E9
lda #$16 / sta $03F2
lda #$D0 / sta $03F5–$03F8
lda #$00 / sta $4011 (APUDMCDIRECT)
lda #$06 / sta $2001 (PPUMASK: show BG)
jsr $C7E6 / jsr $C7CA
jmp $C7B6
```
**`$C7B6` (Label_C7B6)** — Clear score/timer:
```
lda #$00 / sta $0330 / sta $0331
```
Then enters main loop at **`$C7BE`**.
---
## 2. Main Loop — `$C7BE` onward
```
Label_C7BE:
jsr $C7E0
lda Zp10 / and #$E7 / sta Zp10 ; clear bits 3,4,5 of PPUCTRL shadow
... (main game loop continues)
```
The main loop runs continuously, synchronized with NMI via Zp10/Zp11 flags.
---
## 3. Interrupt Vectors — CPU $FFFA–$FFFF
Located at PRG offset $3FFA (end of 16 KB bank):
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C7F1 | `NmiHandler` |
| RESET | $C735 | `Reset` |
| IRQ | $FFF0 | Unused (lands in vector-page padding) |
---
## 4. NMI / Frame Heartbeat — `NmiHandler` ($C7F1)
The NMI fires once per frame and drives the frame pacing:
```
NmiHandler ($C7F1):
lda #$00 / sta $4016 ; controller strobe
lda Zp10 ($10) / and #$7F / sta $2000 / sta Zp10 ; PPUCTRL &= ~$80 (NMI off during frame)
lda Zp11 ($11) / eor #$1A / sta $2001 ; PPUMASK ^= $1A (toggle bits)
jsr $EF4A ; sprite/OAM update
jsr $EF36 ; PPU/scroll update
lda $0396 / bne Label_C819 ; check state
lda $0397 / bne Label_C819
jsr $F085 ; attract mode audio
Label_C819:
jsr $F9A0 ; gameplay audio
jsr $F0BC ; sound effects
lda Zp43 ($43)
bne Label_C827
lda #$FF / sta Zp43 ; init Zp43 = $FF
Label_C827:
lda Zp60 ($60) ; game state
beq Label_C843 ; 0: title/attract
cmp #$01 / beq Label_C849 ; 1: gameplay
cmp #$02 / beq Label_C83D ; 2: bonus/stage clear
jsr $D3F1 / lda #$00 / sta Zp77 / jmp $C84C ; 3+: other
Label_C83D:
jsr $CCD7 / jmp $C84C ; state 2
Label_C843:
jsr $C865 / jmp $C84C ; state 0
Label_C849:
jsr $CA31 ; state 1
Label_C84C:
... (common NMI exit)
```
**NMI State Machine (Zp60 / $60):**
- **0 (Title/Attract):** `$C865` — attract mode logic
- **1 (Gameplay):** `$CA31` — main gameplay update
- **2 (Bonus/Transition):** `$CCD7` — stage clear, bonus
- **3+ (Other):** `$D3F1` — game over, high score, etc.
**Key NMI Operations:**
1. Controller strobe (`$4016`)
2. PPUCTRL/PPUMASK update (Zp10/Zp11 shadows)
3. Sprite/OAM update (`$EF4A`)
4. PPU/Scroll update (`$EF36`)
5. Audio engine (`$F9A0` gameplay, `$F085` attract, `$F0BC` SFX)
6. Game state dispatch via Zp60
---
## 5. Key Subsystems
### 5.1 Sprite/OAM System — `$EF4A` / `$EF36`
- `$EF4A`: Builds OAM at $0200 from object arrays
- `$EF36`: PPU scroll/register updates
### 5.2 Audio Engine
- **Attract:** `$F085` (called when $0396/$0397 set)
- **Gameplay:** `$F9A0` (music), `$F0BC` (sound effects)
- APU registers at $4000–$4013
### 5.3 Gameplay Logic — `$CA31` / `$CCD7` / `$D3F1` / `$C865`
- `$CA31`: Main gameplay (player, millipede, enemies, collision)
- `$CCD7`: Stage clear / bonus round
- `$D3F1`: Game over / high score
- `$C865`: Attract mode demo
### 5.4 Object State — $03D0+ region
- `$03D0–$03F8`: Initialized at reset from $C271 table
- `$0330–$0331`: Score/timer
- `$0396–$0397`: Attract mode flags
- `$03F5–$03F8`: Player position/state
### 5.5 Indirect Dispatch — `$EF15` (`jmp ($0002)`)
`jmp ($0002)` at $EF15 — target address in ZP $0002/$0003, set by caller. Not followed statically.
---
## 6. Memory Map (Key Zero Page / RAM)
| Address | Name | Use |
|---------|------|-----|
| $00–$01 | `Zp00`/`Zp01` | 16-bit pointer (RAM clear) |
| $02–$03 | `Zp02`/`Zp03` | Indirect dispatch pointer (`jmp ($0002)`) |
| $10 | `Zp10` | PPUCTRL shadow (bit 7 = NMI enable) |
| $11 | `Zp11` | PPUMASK shadow (toggled via EOR #$1A) |
| $43 | `Zp43` | Audio state (init $FF) |
| $60 | `Zp60` | Game state (0=attract, 1=play, 2=bonus, 3+=other) |
| $77 | `Zp77` | Gameplay sub-state |
| $0330–$0331 | Score/Timer | Cleared at $C7B6 |
| $0396–$0397 | Attract flags | Checked in NMI for audio path |
| $03D0–$03F8 | Object init state | Copied from $C271 at reset |
| $03DF–$03E4 | Object params | 6 bytes from $C271 |
| $03F5–$03F8 | Player state | Initialized to $D0 |
| $4011 | APUDMCDIRECT | DMC direct load (silenced at reset) |
| $4016 | JOY1 | Controller strobe (NMI) |
---
## 7. Data Tables
| Table | Address | Description |
|-------|---------|-------------|
| Object init data | $C271 (`ObjectInitData`) | 6 bytes copied to $03DF at reset |
| Level data | $C000 (`LevelData`) | 1845 bytes of level/enemy pattern data |
| Audio data | $F24F (`AudioData`) | audio sequences |
| Vector page | $FE61 (`VectorPageData`) | padding/vectors |
| Level/millipede data | $C000–$C734 | 1845 bytes of level layouts, enemy paths (classified as data, referenced by code) |
| Audio sequences | $F000+ | Music/SFX data |
**Note:** The region $C000–$C734 (1845 bytes) is classified as `.byte` data but is heavily referenced by code (93+ references from `lda $Cxxx,x`, `bit $Cxxx` instructions). This is level/map data for the millipede and enemy movement patterns.
---
## 8. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C735) ────► sei/cld, PPUCTRL=0, stack=$FF
│
├─ Wait VBlank (poll $2002 bit 7)
│
├─ Clear $0000–$05FF (6 pages via Zp00/Zp01)
│
├─ jsr $C76A:
│ Clear $03D0–$03F9
│ Copy $C271→$03DF (6 bytes)
│ Init $03D0=$2C, $03E6=$2D, $03E9=$24, $03F2=$16
│ $03F5–$03F8 = $D0
│ APUDMCDIRECT=0, PPUMASK=$06
│ jsr $C7E6, $C7CA
│
├─ PPUCTRL |= $80 (NMI on) → Zp10
│
▼
Label_C764: jsr $F09B / jmp $C764 (wait for first NMI)
│
▼ (NMI fires)
NMI ($C7F1) [once per frame]
├─ Controller strobe ($4016)
├─ Zp10 &= $7F → PPUCTRL (NMI off during frame)
├─ Zp11 ^= $1A → PPUMASK (toggle bits 1,3,4)
├─ jsr $EF4A (sprite/OAM update)
├─ jsr $EF36 (PPU/scroll update)
├─ Check $0396/$0397 → attract audio ($F085) if set
├─ jsr $F9A0 (gameplay audio)
├─ jsr $F0BC (sound effects)
├─ Zp43 init ($FF) if zero
├─ Dispatch on Zp60 ($60):
│ 0: jsr $C865 (attract) → $C84C
│ 1: jsr $CA31 (gameplay) → $C84C
│ 2: jsr $CCD7 (bonus) → $C84C
│ 3+: jsr $D3F1 → $C84C
└─ $C84C: common exit → rti
Main Loop ($C7BE+)
├─ jsr $C7E0
├─ Zp10 &= $E7 (clear bits 3,4,5)
├─ Game logic (player, millipede, enemies)
├─ Collision detection
├─ Level progression
└─ Loop (VBlank synchronized via Zp10/Zp11)
```
---
## 9. Naming and Residue
**Pass 1 complete** — 14,744 lines, canonical audit comments, 74.6% code. All 839 address-derived `Label_` definitions have been renamed: 12 semantic routine names (Reset, NmiHandler, MainLoop, GameplayUpdate, AttractMode, AttractAudio, GameOver, StageClear, InitObjects, ClearScore, SoundEffects, GameplayAudio) + 264 `SubC<addr>` entry points + 577 `Sub_8764_*` internal branch targets. No `Label_` remains.
**Pass 2 complete** — every `jsr`/`jmp` target is a named routine (semantic or `SubC<addr>`); internal branch targets use `<Routine>_Block`/`JoinBranchPaths` style names.
**Pass 3 complete** — 8 semantic zero-page aliases (PtrLo/Hi, Ptr2Lo/Hi, PpuCtrlShadow, PpuMaskShadow, AudioState, GameState, GameplaySubState); hardware register operands substituted via `apply_symbols.py`; data tables named: `LevelData` ($C000–$C734), `ObjectInitData` ($C271), `AudioData` ($F24F–$F984), `VectorPageData` ($FE61–$FFFF). Remaining zero-page slots retain neutral `ZpXX` names where role is not unique.
**Indirect dispatch** (not followed statically): `jmp ($0002)` at $EF15.
**Large data region** — $C000–$C734 (1845 bytes) classified as data but referenced by 93+ code instructions (`lda $Cxxx,x`, `bit $Cxxx`). This is level/enemy pattern data, not code.
**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).