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].
8.2 KiB
Raid on Bungeling Bay (U) — Game Flow from Power-On
This document describes the execution flow of the disassembled Raid on Bungeling Bay (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) → Label_C6E3 ($C6E3)
The RESET vector points to $C000, which contains a single jmp $C6E3. The actual reset routine begins at $C6E3:
| Step | Code | Purpose |
|---|---|---|
| 1 | lda #$10 / sta $2000 / sta ZpC2 |
PPUCTRL = $10 (NMI off, sprite $0000, BG $0000) + shadow in ZpC2 |
| 2–5 | Label_C6EA: inc ZpEA / lda $2002 / bpl |
Wait for VBlank (poll PPUSTATUS bit 7), increment ZpEA |
| 6–7 | ldx #$FF / txs |
Initialize stack pointer to $01FF |
| 8 | inx / txa |
X=0, A=0 |
| 9–13 | Label_C6F6: sta Zp00,x / inx / cpx #$EA / bne |
Clear $0000–$00E9 (234 bytes) |
| 14 | jsr $C98E |
Initialize game systems |
| 15–18 | lda $026D / ora $0273 / cmp #$23 / beq Label_C719 |
Check signature at $026D/$0273; if $23, warm start |
| 19–22 | lda #$23 / ldx #$0B / Label_C70E: sta $0268,x / dex / bpl / lda #$25 / sta $026F |
Cold start: write $23 to $0268–$0273, $25 to $026F |
| 23–26 | Label_C719: ldx #$FF / txs / jsr $C98E / lda #$01 / ldx #$06 |
Warm start: re-init stack, systems, set A=1, X=6 |
| 27+ | Falls through to main loop | Continue initialization |
Warm-start signature: Two bytes at $026D/$0273. If their OR equals $23, the game treats it as a warm start (skips full clear, re-initializes stack and systems).
2. Main Loop
After reset initialization, the main loop runs continuously, synchronized with NMI via ZpC4 flag. The loop calls various gameplay subroutines for helicopter control, enemy AI, base building, etc.
3. Interrupt Vectors — CPU $FFFA–$FFFF
Located at PRG offset $3FFA (end of 16 KB bank):
| Vector | Address | Handler |
|---|---|---|
| NMI | $C04C | NmiHandler |
| RESET | $C000 | Reset (jumps to $C6E3) |
| IRQ | $C2C0 | IrqHandler (target $C2C0 — verify during Pass 1) |
4. NMI / Frame Heartbeat — NmiHandler ($C04C)
The NMI fires once per frame and implements a state machine based on ZpC4 ($C4):
NmiHandler ($C04C):
pha / txa / pha / tya / pha ; preserve A,X,Y
lda ZpC4 ($C4)
bne Label_C05F ; ZpC4≠0: Path B
lda #$00 / sta $2003 (OAMADDR)
lda #$07 / sta $4014 (OAMDMA) ; sprite DMA from $0700
... (Path A: minimal)
Label_C05F: ; Path B: full frame update
lda $0200 / ldx #$00 / stx $0200 ; read/clear $0200
lsr a / bcs Label_C0C1 ; bit 0
lsr a / bcs Label_C0A8 ; bit 1
lsr a / bcs Label_C0A6 ; bit 2
lsr a / bcs Label_C0A6 ; bit 3 (same as bit 2)
lda ZpC4 / bne Label_C0A3 ; if ZpC4 still set
lda Zp55 ($55) / beq Label_C09A ; check Zp55
dec Zp55 / lsr a / bcs Label_C0A0 ; decrement, check bit
lda #$3F / sta $2006 / ldx #$00 / stx $2006 / ldx #$10 / lda #$14
Label_C08E:
jsr $C006 / dex / bne Label_C08E ; write 16 bytes to PPUDATA ($3F00)
jsr $C4E4 / jmp $C17A
Label_C09A:
lda ZpD6 ($D6) / cmp #$02 / bne Label_C0A3
Label_C0A0:
... (palette/sprite updates)
Label_C0A3:
... (gameplay updates)
Label_C0A6:
... (sprite updates)
Label_C0A8:
... (enemy updates)
Label_C0C1:
... (collision/game logic)
Two NMI Paths:
- Path A (ZpC4=0): Minimal — only OAM DMA from $0700
- Path B (ZpC4≠0): Full — reads $0200 as state bitmap, dispatches to handlers for bits 0–3:
- Bit 0 →
Label_C0C1(collision/game logic) - Bit 1 →
Label_C0A8(enemy updates) - Bit 2/3 →
Label_C0A6(sprite updates) - If ZpC4 still set →
Label_C0A3(gameplay) - Zp55 check →
Label_C09A(timer-based) - ZpD6=2 →
Label_C0A0(palette)
- Bit 0 →
Key ZP Flags:
ZpC4($C4): NMI path selector (0=minimal, ≠0=full)Zp55($55): Frame sub-counter/timerZpD6($D6): Game state (2 = palette update)ZpEA($EA): VBlank wait counterZpC2($C2): PPUCTRL shadow
5. Key Subsystems
5.1 PPU Helper — $C006 / $C003
$C006: Write A to PPUDATA ($2007), RTS$C003:lda $0200,x(read from $0200+X)
5.2 Sprite/OAM System
- OAM DMA from $0700 (Path A) or $0200 (Path B)
- Sprite updates at
Label_C0A6,Label_C0A8
5.3 Palette Updates — $C08E / $C4E4
- When Zp55 expires or ZpD6=2: writes 16 bytes to PPU $3F00 (palette)
- Data from $0200+ area
5.4 Gameplay Logic
$C0C1: Collision detection, helicopter physics$C0A8: Enemy/factory AI$C0A6: Sprite animation$C17A: Base/building logic$C98E: System initialization (called at reset and warm start)
5.5 Indirect Dispatch (multiple)
Multiple jmp (zp) not followed statically:
jmp ($001B)at $E7A3jmp ($0017)at $E79Djmp ($0019)at $E7A0jmp ($0001)at $E2EC
Target addresses in ZP set by callers; require runtime tracing.
6. Memory Map (Key Zero Page / RAM)
| Address | Name | Use |
|---|---|---|
| $00 | Zp00 |
RAM clear pointer / general |
| $01 | Zp01 |
RAM clear page counter |
| $1B | Zp1B |
Indirect dispatch ($001B) |
| $17 | Zp17 |
Indirect dispatch ($0017) |
| $19 | Zp19 |
Indirect dispatch ($0019) |
| $01 | Zp01 |
Indirect dispatch ($0001) |
| $55 | Zp55 |
Frame sub-counter/timer |
| $C2 | ZpC2 |
PPUCTRL shadow |
| $C4 | ZpC4 |
NMI path selector (0=minimal, ≠0=full) |
| $D6 | ZpD6 |
Game state (2 = palette) |
| $EA | ZpEA |
VBlank wait counter |
| $0200 | State bitmap | Read in NMI Path B (bits 0–3 dispatch) |
| $0268–$0273 | Warm-start signature | $23 bytes for warm start detection |
| $026D/$0273 | Signature check | OR'd together, compared to $23 |
| $026F | Zp6F |
Gameplay parameter ($25 init) |
| $0700 | OAM source (Path A) | Sprite DMA from $0700 when ZpC4=0 |
7. Summary of Execution Flow
Power On / Reset
│
▼
Reset ($C000) ────► jmp $C6E3
│
▼
Label_C6E3 ($C6E3) ──► PPUCTRL=$10, wait VBlank, stack=$FF
│
├─ Clear $0000–$00E9 (234 bytes)
├─ jsr $C98E (system init)
├─ Check $026D/$0273 signature:
│ ├─ OR = $23: Warm start
│ │ ldx #$FF / txs / jsr $C98E / A=1, X=6
│ └─ Else: Cold start
│ Write $23 to $0268–$0273, $25 to $026F
│
▼
Main Loop (VBlank synchronized via ZpC4)
│
▼ (NMI fires once per frame)
NMI ($C04C)
├─ Preserve A,X,Y
├─ lda ZpC4
│ ├─ =0 (Path A): OAM DMA from $0700, minimal
│ └─ ≠0 (Path B):
│ lda $0200 / stx $0200 (read/clear state bitmap)
│ lsr ×4 → dispatch on bits 0–3:
│ bit 0: Label_C0C1 (collision/logic)
│ bit 1: Label_C0A8 (enemies)
│ bit 2/3: Label_C0A6 (sprites)
│ ZpC4 still set → Label_C0A3 (gameplay)
│ Zp55=0 → Label_C09A (timer)
│ dec Zp55, check bit → Label_C0A0 (palette)
│ ZpD6=2 → Label_C0A0 (palette)
│ Palette: $3F00, 16 bytes via $C006
│
└─ Restore, rti
8. Naming and Residue
Pass 1 complete — 14,396 lines, 868 Label_ definitions, canonical audit comments, 64.6% code. 6 seeded routine entries from table references.
Pass 2 outstanding — No semantic names applied; all 868 labels remain address-derived Label_XXXX.
Pass 3 outstanding — No semantic zero-page aliases; locals.inc holds neutral ZpXX names.
Indirect dispatch (not followed statically):
jmp ($001B)at $E7A3jmp ($0017)at $E79Djmp ($0019)at $E7A0jmp ($0001)at $E2EC
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).