# Popeye (JU) (PRG 1) — Game Flow from Power-On This document describes the execution flow of the disassembled Popeye (JU) (PRG 1) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `prg.s` in the repository. The rebuild is verified **byte-identical** to the whole original ROM (`reconstructed.nes` vs `rom.nes`, SHA-256 `00160651…78bd8b4` — promoted from `_wip/NROM/` 2026-10-20). **Companion note:** `Popeye (JU) (PRG 0)` holds a different PRG image of the same title (different mirroring flag: Vertical vs Horizontal). Neither is currently known to be a superset of the other. --- ## 1. Reset Vector — CPU $FFFC → $C649 **`Reset`** ($C649–$C683) **Identical to PRG 0** — 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 (identical to PRG 0): ``` 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 Identical to PRG 0 — main loop runs synchronized with NMI via Zp10/Zp60/Zp63 flags. --- ## 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) **Identical to PRG 0** — 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 | --- ## 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 — `$ED03` (`jmp ($0002)`) `jmp ($0002)` at $ED03 — target address in ZP $0002/$0003, set by caller. Not followed statically. (Address differs slightly from PRG 0's $ECFF.) --- ## 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 **Identical to PRG 0** — See `Popeye (JU) (PRG 0)/README.md` for full flow diagram. The only differences between PRG 0 and PRG 1 are: 1. **Mirroring:** PRG 0 = Vertical (header byte 6 = `$01`), PRG 1 = Horizontal (header byte 6 = `$00`) 2. **iNES header cleanup:** PRG 0 carries `NI2.1` junk in header bytes 10–15; PRG 1's header is clean zeros 3. **PRG bytes differ slightly** — Different build/revision 4. **Indirect dispatch address:** PRG 0 at $ECFF, PRG 1 at $ED03 5. **Seeded routine entries:** PRG 0 has `$EF19, $EF3A, $EFBF, $EFE0, $F002, $F071`; PRG 1 has `$EF1D, $EF3E, $EFC3, $EFE4, $F006, $F057` (offset by a few bytes) --- ## 9. Naming and Residue **Pass 1 complete** — 14,855 lines, canonical audit comments, 72.3% code. 6 seeded routine entries from table references. **Pass 2 complete** — all 876 `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`; 80 operands substituted via `apply_symbols.py` (15 single + 65 hardware); data tables named: `LevelData` ($C000–$C648), `AudioData` ($F08C–$F832), `VectorPageData` ($FCCE–$FFEF). Remaining zero-page slots retain neutral `ZpXX` names. **Indirect dispatch** (not followed statically): `jmp ($0002)` at $ED03. **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 (2026-10-20)** — moved out of `_wip/NROM/` after Pass 2/3: `header.s`/`chr.s` added (`header.bin`/`chr.bin` two-line `.incbin`s), `linker.cfg` extended with the `HEADER` memory area, `rom.nes` added, and `make` switched to a whole-ROM `cmp`. Build verified: `reconstructed.nes` = SHA-256 `0016065132a2dc98afb1181a735edcb9c656b0b89af4e0771e94807ba67bd8b4`, identical to `rom.nes` and the original `Popeye (JU) (PRG 1) [!].nes`.