# Popeye (JU) (PRG 0) — Game Flow from Power-On This document describes the execution flow of the disassembled Popeye (JU) (PRG 0) 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. **Companion note:** `Popeye (JU) (PRG 1)` holds a different PRG image of the same title (different mirroring flag: Horizontal vs Vertical). Neither is currently known to be a superset of the other. --- ## 1. Reset Vector — CPU $FFFC → $C649 **`Reset`** ($C649–$C683) On power-on or reset, the 6502 jumps to **$C649** (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 #$10` / `sta $2000` | PPUCTRL = $10 (NMI off, sprite $0000, BG $0000, 8×8) | | 5–6 | `ldx #$FF` / `txs` | Initialize stack pointer to $01FF | | 7–11 | `Label_C653`: poll `$2002` via `and #$80` / `beq` | Wait for VBlank (bit 7 of PPUSTATUS) | | 12–13 | `ldy #$07` / `sty Zp01` / `ldy #$00` / `sty Zp00` | Zp00/Zp01 = $0700 (RAM clear pointer) | | 14 | `tya` | A = 0 | | 15–19 | `Label_C663`: `sta (Zp00),y` / `dey` / `bne` / `dec Zp01` / `bpl` | Clear $0000–$07FF (8 pages) | | 20 | `jsr $C686` | Initialize player/object state at $0500–$0504 | | 21 | `lda #$01` / `sta Zp60` | Zp60 ($60) = 1 (game state) | | 22 | `lda #$00` / `sta Zp63` | Zp63 ($63) = 0 | | 23 | `lda Zp10` / `eor #$80` / `sta $2000` / `sta Zp10` | PPUCTRL ^= $80 (toggle NMI enable) + shadow | | 24 | `Label_C680`: `jsr $EE74` / `jmp $C680` | Infinite loop: waits for first NMI | **`$C686` (Label_C686)** — Player state init: ``` lda #$11 / sta $0500 / sta $0502 lda #$03 / sta $0501 lda #$01 / sta $0503 ldx #$19 / lda #$24 Label_C69C: sta $0504,x ; fill $0504–$051C with $24 (25 bytes) dex / bne Label_C69C ldx #$00 ``` Then falls through to `$C6A4` (main loop entry). --- ## 2. Main Loop — `$C6A4` onward After reset initialization, the main loop runs at `$C6A4`, synchronized with NMI via Zp10/Zp60/Zp63 flags. The loop calls various gameplay subroutines and waits for NMI to signal frame completion. --- ## 3. Interrupt Vectors — CPU $FFFA–$FFFF Located at PRG offset $3FFA (end of 16 KB bank): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C701 | `NmiHandler` | | RESET | $C649 | `Reset` | | IRQ | $FFF0 | `IrqHandler` (single `sta (Zp16,x)` — undocumented opcode $81, effectively unused) | --- ## 4. NMI / Frame Heartbeat — `NmiHandler` ($C701) The NMI fires once per frame and implements a state machine dispatching on Zp30/Zp31/Zp60/Zp63: ``` NmiHandler ($C701): lda Zp10 ($10) / and #$7F / sta $2000 / sta Zp10 ; PPUCTRL &= ~$80 (NMI off during frame) lda Zp11 ($11) / eor #$18 / sta $2001 ; PPUMASK ^= $18 (toggle bits 3,4) jsr $ED34 ; sprite/OAM update jsr $ED20 ; PPU/scroll update lda $0400 / bne Label_C724 ; check $0400 lda $0401 / bne Label_C724 ; check $0401 jsr $EE5E ; attract mode audio Label_C724: jsr $F970 ; gameplay audio jsr $EE89 ; sound effects lda Zp43 ($43) bne Label_C732 lda #$FF / sta Zp43 ; init Zp43 = $FF Label_C732: lda Zp30 ($30) beq Label_C73C jsr $CA8B / jmp $C75D ; state: Zp30≠0 Label_C73C: lda Zp31 ($31) beq Label_C746 jsr $CAD4 / jmp $C75D ; state: Zp31≠0 Label_C746: lda Zp60 ($60) bne Label_C754 lda Zp63 ($63) beq Label_C75A jsr $CB12 / jmp $C75D ; state: Zp60=0, Zp63≠0 Label_C754: jsr $C76A / jmp $C75D ; state: Zp60≠0 Label_C75A: jsr $C89B ; state: Zp60=0, Zp63=0 Label_C75D: ... (common NMI exit → rti) ``` **NMI State Machine:** | Condition | Handler | Description | |-----------|---------|-------------| | $0400≠0 or $0401≠0 | `$EE5E` | Attract mode audio | | Zp30≠0 | `$CA8B` | Gameplay mode A | | Zp31≠0 | `$CAD4` | Gameplay mode B | | Zp60≠0 | `$C76A` | Gameplay mode C | | Zp60=0, Zp63≠0 | `$CB12` | Bonus/stage transition | | Zp60=0, Zp63=0 | `$C89B` | Title/attract mode | **Key NMI Operations:** 1. PPUCTRL &= $7F (disable NMI during frame), PPUMASK ^= $18 2. Sprite/OAM update (`$ED34`), PPU/scroll update (`$ED20`) 3. Audio: attract (`$EE5E`) or gameplay (`$F970` + `$EE89`) 4. State dispatch via Zp30/Zp31/Zp60/Zp63 5. Zp43 init to $FF on first gameplay NMI --- ## 5. Key Subsystems ### 5.1 Sprite/OAM System — `$ED34` / `$ED20` - `$ED34`: Builds OAM at $0200 from object arrays - `$ED20`: PPU scroll/register updates ### 5.2 Audio Engine - **Attract:** `$EE5E` (called when $0400/$0401 set) - **Gameplay:** `$F970` (music), `$EE89` (sound effects) - APU registers at $4000–$4013 ### 5.3 Gameplay Logic - `$CA8B` / `$CAD4` / `$C76A` / `$CB12` / `$C89B`: State-specific gameplay updates - Player (Popeye), enemies (Bluto, Sea Hag), projectiles, collectibles (spinach, hearts, letters) ### 5.4 Level Data — $C000–$C648 region - 1609 bytes classified as `.byte` data but referenced by 148+ code instructions - Contains level layouts, enemy paths, platform positions - Referenced via `lda $Cxxx,x`, `bit $Cxxx` — this is **level/map data**, not code ### 5.5 Indirect Dispatch — `$ECFF` (`jmp ($0002)`) `jmp ($0002)` at $ECFF — 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 #$18) | | $30–$31 | `Zp30`/`Zp31` | Gameplay state selectors | | $43 | `Zp43` | Audio state (init $FF) | | $60 | `Zp60` | Game state (set 1 at reset) | | $63 | `Zp63` | Sub-state (cleared at reset) | | $0200 | OAM buffer | Sprite DMA source | | $0400–$0401 | Attract flags | Checked in NMI for audio path | | $0500–$051C | Player state | Initialized at $C686 ($11,$03,$11,$24×25) | --- ## 7. Data Tables | Table | Address | Description | |-------|---------|-------------| | Level/map data | $C000–$C648 | 1609 bytes of level layouts, enemy paths (referenced by code) | | Audio sequences | $F970+ | Music/SFX data | --- ## 8. Summary of Execution Flow ``` Power On / Reset │ ▼ Reset ($C649) ────► sei/cld, PPUCTRL=$10, stack=$FF │ ├─ Wait VBlank (poll $2002 bit 7) │ ├─ Clear $0000–$07FF (8 pages via Zp00/Zp01) │ ├─ jsr $C686: │ $0500=$11, $0501=$03, $0502=$11, $0503=$01 │ $0504–$051C = $24 (25 bytes) │ ├─ Zp60=1, Zp63=0 ├─ PPUCTRL ^= $80 (enable NMI) → Zp10 │ ▼ Label_C680: jsr $EE74 / jmp $C680 (wait for first NMI) │ ▼ (NMI fires) NMI ($C701) [once per frame] ├─ Zp10 &= $7F → PPUCTRL (NMI off during frame) ├─ Zp11 ^= $18 → PPUMASK (toggle bits 3,4) ├─ jsr $ED34 (sprite/OAM update) ├─ jsr $ED20 (PPU/scroll update) ├─ Check $0400/$0401: │ ├─ Set: jsr $EE5E (attract audio) │ └─ Clear: │ jsr $F970 (gameplay audio) │ jsr $EE89 (sound effects) │ Zp43 init ($FF) │ Dispatch on Zp30/Zp31/Zp60/Zp63: │ Zp30≠0 → $CA8B │ Zp31≠0 → $CAD4 │ Zp60≠0 → $C76A │ Zp60=0,Zp63≠0 → $CB12 │ Zp60=0,Zp63=0 → $C89B └─ rti Main Loop ($C6A4+) ├─ Gameplay logic (player, enemies, collectibles) ├─ Collision detection ├─ Level progression └─ Loop (VBlank synchronized via Zp10/Zp60/Zp63) ``` --- ## 9. Naming and Residue **Pass 1 complete** — 14,875 lines, canonical audit comments, 72.3% code. 6 seeded routine entries from table references. **Pass 2 complete** — all 878 `Label_` definitions renamed: 2 semantic entry names (`Reset`, `NmiHandler`) + 315 `SubC` entry points + 561 `_Block_` internal branch targets. No `Label_` remains. Every `jsr`/`jmp` target is a named routine. **Pass 3 complete** — 4 semantic zero-page aliases (PtrLo/Hi, Ptr2Lo/Hi) added to `locals.inc`; 79 operands substituted via `apply_symbols.py` (15 single + 64 hardware); data tables named: `LevelData` ($C000–$C648), `AudioData` ($F088–$F832), `VectorPageData` ($FCCE–$FFEF). Remaining zero-page slots retain neutral `ZpXX` names. **Indirect dispatch** (not followed statically): `jmp ($0002)` at $ECFF. **Large data region** — $C000–$C648 (1609 bytes) classified as data but referenced by 148+ code instructions. This is level/enemy pattern data. **PRG 0 vs PRG 1** — Same vectors, same structure. PRG 0: Vertical mirroring. PRG 1: Horizontal mirroring. PRG bytes differ slightly (different build). **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).