Files
disasm/_wip/Raid on Bungeling Bay (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

8.2 KiB
Raw Blame History

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)

Key ZP Flags:

  • ZpC4 ($C4): NMI path selector (0=minimal, ≠0=full)
  • Zp55 ($55): Frame sub-counter/timer
  • ZpD6 ($D6): Game state (2 = palette update)
  • ZpEA ($EA): VBlank wait counter
  • ZpC2 ($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 $E7A3
  • jmp ($0017) at $E79D
  • jmp ($0019) at $E7A0
  • jmp ($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 $E7A3
  • jmp ($0017) at $E79D
  • jmp ($0019) at $E7A0
  • jmp ($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).