# Pac-Man (Tengen) (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Pac-Man (Tengen) (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. **Variant note:** `Pac-Man (U)` is an unrelated dump (different PRG). Both use horizontal mirroring. --- ## 1. Reset Vector — CPU $FFFC → $C033 **`Reset`** ($C033–$C0A0) On power-on or reset, the 6502 jumps to **$C033** (non-$C000 entry). 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` / `sta $2001` | PPUCTRL=0, PPUMASK=0 (disable NMI, rendering) | | 5–8 | `Label_C03D`: poll `$2002` via `bpl *-3` | Wait for VBlank | | 9–10 | `ldx #$FF` / `txs` | Initialize stack pointer to $01FF | | 11–12 | `lda #$00` / `tay` | A=0, Y=0 | | 13–17 | `Label_C048`: `sta $0000,y` / `iny` / `cpy #$3E` / `bne` | Clear $0000–$003D (62 bytes) | | 18–25 | `ldx #$08` / `ldy #$87` / `Label_C054`: `sta (Zp00),y` / `iny` / `bne` / `inc Zp01` / `cpx Zp01` / `bne` | Clear 9 pages from $8700 ($8700–$8F00) via Zp00/Zp01 | | 26–27 | `lda #$06` / `sta $2001` | PPUMASK = $06 (show BG, hide sprites) | | 28–31 | `lda #$00` / `sta $2005` ×2 | PPUSCROLL = 0,0 | | 32–33 | `sta Zp41` / `sta Zp42` | Zp41=Zp42=0 (scroll shadows) | | 34–35 | `sta $2000` / `sta $2001` | PPUCTRL=0, PPUMASK=0 | | 36 | `tay` | Y=0 | | 37–45 | `Label_C077`: `lda $C0EB,y` / `cmp $0052,y` / `bne Label_C086` / `iny` / `cpy #$0F` / `bne` / `beq Label_C0A0` | Compare 15-byte copyright string at $C0EB with RAM at $0052; if match, jump to $C0A0 | | 46–49 | `Label_C086`: `lda #$00` / `tay` / `Label_C089`: `sta $0000,y` / `iny` / `bne` / `ldy #$00` | If mismatch: clear $0000–$00FF, Y=0 | | 50+ | `Label_C0A0`... | Continue initialization (title screen, game setup) | **Copyright check:** 15-byte string "COPYRIGHT 1984 NAMCO LTD. ALL RIGHTS RESERVED" at $C0EB compared against RAM at $0052. On cold start, RAM is garbage so check fails, clearing $0000–$00FF. On warm reset, if signature persists, skips extra clear. --- ## 2. Interrupt Vectors — CPU $FFFA–$FFFF Located at PRG offset $3FFA (end of 16 KB bank): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C0FA | `NmiHandler` | | RESET | $C033 | `Reset` | | IRQ | $C16A | `IrqHandler` (single `rti` — unused placeholder) | --- ## 3. NMI / Frame Heartbeat — `NmiHandler` ($C0FA) The NMI fires once per frame and handles PPU updates, controller input, and audio: ``` NmiHandler ($C0FA): pha / txa / pha / tya / pha ; preserve A,X,Y lda #$1E / sta $2001 ; PPUMASK = $1E (BG+sprites on, left 8px) lda #$00 / sta $2003 (OAMADDR) sta Zp40 ($40) ; clear Zp40 lda Zp45 ($45) / sta $4014 (OAMDMA) ; sprite DMA from Zp45 page jsr $DE3D ; sprite/OAM update jsr $E261 ; game logic update jsr $C16B ; palette/PPU update lda Zp41 / sta $2005 ; PPUSCROLL X lda Zp42 / sta $2005 ; PPUSCROLL Y lda Zp47 / and Zp46 / and #$01 ; check game state flags asl a / ora Zp43 ; combine flags sta $2000 ; PPUCTRL update lda #$01 / sta $4016 ; controller strobe on lda #$00 / sta $4016 ; controller strobe off ldx #$08 Label_C13B: lda $4016 / and #$01 / cmp #$01 / ror Zp4D ; read player 1 buttons → Zp4D lda $4017 / and #$01 / cmp #$01 / ror Zp4E ; read player 2 buttons → Zp4E dex / bne Label_C13B ; 8 bits each ldx Zp4D lda Zp46 / and Zp47 beq Label_C15A ldx Zp4E ; if Zp46&Zp47: use player 2 input Label_C15A: stx Zp4F ; store combined input inc Zp4B ; frame counter lda Zp48 bne Label_C165 jsr $EE8F ; attract mode logic Label_C165: pla / tay / pla / tax / pla / rti ; restore, return (IRQ handler follows) ``` **Key ZP Flags:** - `Zp40` ($40): Cleared each NMI - `Zp41`/$42 ($41/$42): Scroll X/Y shadows - `Zp43` ($43): PPUCTRL flag (bit 0) - `Zp45` ($45): OAM DMA source page - `Zp46`/$47 ($46/$47): Game state flags - `Zp4D`/$4E ($4D/$4E): Player 1/2 button state (shifted in) - `Zp4F` ($4F): Active player input - `Zp4B` ($4B): Frame counter --- ## 4. Key Subroutines ### 4.1 `$DE3D` — Sprite/OAM Update Builds OAM at $0200 from object state. ### 4.2 `$E261` — Main Game Logic Pac-Man movement, ghost AI, collision, dots, power pellets. ### 4.3 `$C16B` — Palette/PPU Update ``` Label_C16B: lda $0299 / cmp #$0F / bne Label_C18C lda $2002 / lda #$3F / sta $2006 / lda #$00 / sta $2006 ldy #$00 Label_C181: lda $0299,y / sta $2007 / iny / cpy #$10 / bne Label_C181 ``` When $0299=$0F, uploads 16 bytes from $0299 to PPU palette ($3F00). ### 4.4 `$EE8F` — Attract Mode Called when Zp48=0 (title screen). ### 4.5 Controller Reading 8-bit shift register read from $4016 (player 1) and $4017 (player 2), results in Zp4D/Zp4E, combined to Zp4F based on Zp46/Zp47. --- ## 5. Indirect Dispatch Multiple `jmp ($0010)` at $C250, $D90F, $D9B6, $D3CA — target in ZP $0010/$0011 set by caller. One `jmp ($00FE)` at $EFDA — target in ZP $00FE/$00FF. Not followed statically; requires runtime tracing. --- ## 6. Memory Map (Key Zero Page / RAM) | Address | Name | Use | |---------|------|-----| | $10 | `Zp10` | General purpose | | $40 | `Zp40` | Cleared each NMI | | $41–$42 | `Zp41`/`Zp42` | Scroll X/Y shadows | | $43 | `Zp43` | PPUCTRL flag | | $45 | `Zp45` | OAM DMA source page | | $46–$47 | `Zp46`/`Zp47` | Game state flags | | $48 | `Zp48` | Attract mode flag | | $4B | `Zp4B` | Frame counter | | $4D–$4E | `Zp4D`/`Zp4E` | Player 1/2 button shift registers | | $4F | `Zp4F` | Active input | | $0200 | OAM buffer | Sprite DMA source | | $0299 | Palette buffer | 16 bytes for $3F00 upload | | $0052 | Copyright check | 15-byte RAM signature | --- ## 7. Data Tables | Table | Address | Description | |-------|---------|-------------| | Copyright string | $C0EB | 15 bytes "COPYRIGHT 1984 NAMCO LTD. ALL RIGHTS RESERVED" | | Palette upload | $0299 | 16 bytes, uploaded when $0299=$0F | | Ghost AI / level data | $C000+ | Referenced by indirect dispatch | --- ## 8. Summary of Execution Flow ``` Power On / Reset │ ▼ Reset ($C033) ────► sei/cld, PPU off, wait VBlank, stack=$FF │ ├─ Clear $0000–$003D (62 bytes) ├─ Clear $8700–$8F00 (9 pages via Zp00/Zp01) ├─ PPUMASK=$06, PPUSCROLL=0,0 ├─ Zp41=Zp42=0 ├─ PPUCTRL=0, PPUMASK=0 ├─ Copyright check: $C0EB vs $0052 (15 bytes) │ ├─ Match: jump to Label_C0A0 │ └─ Mismatch: clear $0000–$00FF │ ▼ Main Initialization (Label_C0A0+) │ ▼ Main Loop (VBlank synchronized) │ ▼ (NMI fires once per frame) NMI ($C0FA) ├─ Preserve A,X,Y ├─ PPUMASK=$1E, OAMADDR=0, Zp40=0 ├─ OAM DMA from Zp45 page ├─ jsr $DE3D (sprites), $E261 (game logic), $C16B (palette) ├─ Scroll from Zp41/Zp42 ├─ PPUCTRL from Zp43/Zp46/Zp47 ├─ Controller strobe + 8-bit read ($4016/$4017) → Zp4D/Zp4E ├─ Select active input → Zp4F ├─ inc Zp4B (frame counter) ├─ Zp48=0 → jsr $EE8F (attract) └─ Restore, rti ``` --- ## 9. Naming and Residue **Pass 1 complete** — 4,482 lines, 192 `Label_` definitions, canonical audit comments, 19.5% code. 16 seeded routine entries from table references. **Pass 2 outstanding** — No semantic names applied; all 192 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 ($0010)` at $C250, $D90F, $D9B6, $D3CA - `jmp ($00FE)` at $EFDA **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).