# Pac-Man (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Pac-Man (U) 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 original ROM** — `make` compares the whole `reconstructed.nes` against `rom.nes`. **Variant note:** `Pac-Man (Tengen) (U)` is an unrelated dump (different PRG). Both use horizontal mirroring. **RESET vector** — Points to **$FF00** (inside the PRG bank, valid on NROM since the full $C000–$FFFF is mapped). The code at $FF00 performs cold-start initialization: disables interrupts, silences the PPU, waits for vblank twice, then jumps to **ColdStart** at $C042. --- ## 1. Reset Vector — CPU $FFFC → $FF00 → ColdStart ($C042) **`Reset`** ($FF00–$FF14) — On power-on or reset, the 6502 jumps to **$FF00** (valid in NROM since the full $C000–$FFFF bank is mapped). 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` | Silence PPU (PPUCTRL=0, PPUMASK=0) | | 5–6 | `lda $2002` / `bpl` ×2 | Wait for VBlank twice | | 7 | `jmp $C042` | Jump to ColdStart | **`ColdStart`** ($C042–$C0A0) — Continues cold-start hardware initialization: | Step | Code | Purpose | |------|------|---------| | 1–2 | `ldx #$FF` / `txs` | Initialize stack pointer to $01FF | | 3–4 | `lda #$00` / `tay` | A=0, Y=0 | | 5–9 | `ClearLowRAM`: `sta a:Zp00,y` / `iny` / `cpy #$3E` / `bne` | Clear $0000–$003D (62 bytes) | | 10–17 | `ClearHighRAMPages`: clear 9 pages from $8700 via Zp00/Zp01 | Clear $8700–$8F00 | | 18–19 | `lda #$06` / `sta $2001` | PPUMASK = $06 (show BG, hide sprites) | | 20–23 | `lda #$00` / `sta $2005` ×2 | PPUSCROLL = 0,0 | | 24–25 | `sta ScrollX` / `sta ScrollY` | ScrollX=ScrollY=0 | | 26–27 | `sta $2000` / `sta $2001` | PPUCTRL=0, PPUMASK=0 | | 28 | `tay` | Y=0 | | 29–37 | `CheckCopyrightSig`: compare 15-byte signature at $C0EB with RAM at $0052 | If match, jump to PostSignatureCheck | | 38–41 | `ClearZPOnMismatch`: clear $0000–$00FF, Y=0 | If mismatch: extra ZP clear | **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. **Note:** The code from $C042 onward is **identical** to `Pac-Man (Tengen) (U)` from its `Label_C03D` ($C03D) onward (that dump has not been through Pass 2 yet, so its labels are still address-derived). The only difference is the entry point ($C033 vs $C042) and the initial `sei`/`cld`/`PPU off` which are missing here (possibly executed before jumping to $C042). --- ## 2. Interrupt Vectors — CPU $FFFA–$FFFF Located at PRG offset $3FFA (end of 16 KB bank): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C0FA | `NmiHandler` | | RESET | $FF00 | `Reset` — real boot code inside the PRG bank (see §1), not padding | | IRQ | $C16A | `IrqHandler` (single `rti` — unused placeholder) | **Actual reset entry:** `Reset` ($FF00) → `ColdStart` ($C042). --- ## 3. NMI / Frame Heartbeat — `NmiHandler` ($C0FA) Identical to `Pac-Man (Tengen) (U)` — see that document for full details. Summary: ``` NmiHandler ($C0FA): pha / txa / pha / tya / pha lda #$1E / sta PPUMASK lda #$00 / sta OAMADDR / sta NmiFlag lda OamDmaPage / sta OAMDMA jsr FlushGameplayPpuUpdates / jsr ReadNearbyNametableTiles / jsr UpdatePalettes lda ScrollX/ScrollY → PPUSCROLL (scroll) lda ActivePlayerFlag/CocktailModeFlag/PpuCtrlShadow → PPUCTRL Controller strobe + 8-bit read (JOY1/JOY2) → Player1Input/Player2Input Select active input → ActiveInput inc FrameCounter (frame counter) GameplaySuspended=0 → jsr UpdateAudioVoices (sound) pla / tay / pla / tax / pla / rti ``` --- ## 4. Key Subroutines Identical to `Pac-Man (Tengen) (U)`: - `FlushGameplayPpuUpdates` ($DE3D) — flushes the pending VRAM update blocks at $023F/$0245 to the PPU - `ReadNearbyNametableTiles` ($E261) — reads the nametable tiles around Pac-Man back through PPUDATA - `UpdatePalettes` ($C16B) — Palette/PPU update (uploads 16 bytes from $0299 when $0299=$0F) - `UpdateAudioVoices` ($EE8F) — audio engine: walks the 16 voice slots at $0620 and starts/stops the channels the gameplay sound script asks for - Controller reading: 8-bit shift from JOY1/JOY2 → Player1Input/Player2Input → ActiveInput --- ## 5. Indirect Dispatch Same as Tengen variant: - `jmp (DispatchPtrLo)` at $C250, $D90F, $D9B6, $D3CA - `jmp (AudioHandlerPtrLo)` at $EFDA Not followed statically. --- ## 6. Memory Map (Key Zero Page / RAM) | Address | Name | Use | |---------|------|-----| | $00–$01 | `WorkPtrLo`/`WorkPtrHi` (`Zp00`/`Zp01`) | Generic 16-bit pointer (RAM clear, PPU address setup) | | $10–$11 | `DispatchPtrLo`/`DispatchPtrHi` (`Zp10`/`Zp11`) | Indirect jump pointer for `jmp ($0010)` dispatch | | $12–$13 | `Zp12`/`Zp13` | 16-bit scratch word used by `Add16BitWords`/`Subtract16BitWords` while `BuildOamBuffer` computes each sprite's tile address | | $14–$15 | `Zp14`/`Zp15` | 16-bit scratch word: the addend/subtrahend of the same two helpers | | $3F | `GameState` (`Zp3F`) | Game state / mode selector (indexes dispatch table) | | $40 | `NmiFlag` (`Zp40`) | NMI frame flag (0=wait, 1=frame ready) | | $41–$42 | `ScrollX`/`ScrollY` (`Zp41`/`Zp42`) | PPU scroll X/Y shadows | | $43 | `PpuCtrlShadow` (`Zp43`) | PPUCTRL mirror | | $45 | `OamDmaPage` (`Zp45`) | OAM DMA source page ($07 = $0700 page) | | $46 | `CocktailModeFlag` (`Zp46`) | Cocktail / 2-player mode flag | | $47 | `ActivePlayerFlag` (`Zp47`) | Active player selector | | $48 | `GameplaySuspended` (`Zp48`) | $FF = demo/attract mode, 0 = gameplay active | | $4B | `FrameCounter` (`Zp4B`) | Frame counter (incremented each NMI) | | $4C | `StateTimer` (`Zp4C`) | State timer | | $4D | `Player1Input` (`Zp4D`) | Player 1 controller shift register | | $4E | `Player2Input` (`Zp4E`) | Player 2 controller shift register | | $4F | `ActiveInput` (`Zp4F`) | Currently selected player input | | $50 | `EntityIndex` (`Zp50`) | Current entity slot index (ghost 0-3) | | $51 | `OtherEntityIndex` (`Zp51`) | Other entity slot index for collision checks | | $64 | `CopyrightVerified` (`Zp64`) | Set to 1 after copyright signature copied to RAM | | $B7 | `AnimTimer` (`ZpB7`) | Animation frame timer (incremented per entity) | | $CD–$CE | `EntityDeltaX`/`EntityDeltaY` (`ZpCD`/`ZpCE`) | Entity position delta (subtraction result) | | $E3 | `AnimFrameCounter` (`ZpE3`) | Animation frame counter (decremented per frame) | | $E4 | `AnimFrameIndex` (`ZpE4`) | Animation frame index into tile table | | $EF | `AudioSeqPtr` (`ZpEF`) | Audio sequence data pointer (indirect read) | | $F0–$F1 | `AudioChanPtr1Lo`/`AudioChanPtr1Hi` (`ZpF0`/`ZpF1`) | Audio channel 1 data pointer (indirect write) | | $F2–$F3 | `AudioChanPtr2Lo`/`AudioChanPtr2Hi` (`ZpF2`/`ZpF3`) | Audio channel 2 data pointer (indirect write) | | $FC | `AudioChanIndex` (`ZpFC`) | Audio channel loop index | | $FD | `AudioFrameCounter` (`ZpFD`) | Audio frame counter (incremented by 8 per frame) | | $FE–$FF | `AudioHandlerPtrLo`/`AudioHandlerPtrHi` (`ZpFE`/`ZpFF`) | Audio handler indirect jump pointer (`jmp ($00FE)`) | | $0200 | OAM buffer | Sprite DMA source | | $023F | Palette flag | $FF = palette needs upload | | $0299 | Palette buffer 1 | 16 bytes for $3F00 upload | | $02A9 | Palette buffer 2 | 16 bytes for $3F10 upload | | $0052 | Copyright check | 15-byte RAM signature | | $F0–$FF | `ZpF0`–`ZpFF` | Audio engine workspace | | $0200 | OAM buffer | Sprite DMA source | | $023F | Palette flag | $FF = palette needs upload | | $0245–$024A | Palette buffer 2 | Secondary palette data | | $024B–$0250 | Palette buffer 1 | Primary palette data | | $026B–$0270 | PPU address buffer 1 | VRAM write address | | $0272–$0277 | PPU address buffer 2 | VRAM write address | | $028C–$0293 | Ghost target data | Ghost AI target positions | | $0299 | Palette buffer | 16 bytes for $3F00 upload | | $02A9 | Palette buffer | 16 bytes for $3F10 upload | | $0052 | Copyright check | 15-byte RAM signature | --- ## 7. Data Tables The full, evidence-based inventory of every data block in the PRG is in [§11. Pass 3 data-table inventory](#11-pass-3-data-table-inventory). | Table | Address | Description | |-------|---------|-------------| | Programmer signature | $C0EB | 15 bytes, "HIROKI AOYAGI" + `$AA $55`; compared with the 15 bytes at `$0052` | | Palette upload | $0299 | 16 bytes, uploaded when $0299=$0F | --- ## 8. Summary of Execution Flow ``` Power On / Reset │ ▼ (RESET vector points to $FF00 — real boot code in the PRG bank) │ ▼ (sei / cld / PPU off / wait vblank ×2, then jmp ColdStart) ColdStart ($C042) ────► stack=$FF │ ├─ Clear $0000–$003D (62 bytes) ├─ Clear $8700–$8F00 (9 pages via Zp00/Zp01) ├─ PPUMASK=$06, PPUSCROLL=0,0 ├─ ScrollX=ScrollY=0 ├─ PPUCTRL=0, PPUMASK=0 ├─ Copyright check: $C0EB vs $0052 (15 bytes) │ ├─ Match: jump to PostSignatureCheck │ └─ Mismatch: clear $0000–$00FF │ ▼ Main Initialization (PostSignatureCheck $C0A0+) │ ▼ Main Loop (VBlank synchronized) │ ▼ (NMI fires once per frame) NMI ($C0FA) [identical to Tengen variant] ├─ Preserve A,X,Y ├─ PPUMASK=$1E, OAMADDR=0, NmiFlag=0 ├─ OAM DMA from OamDmaPage ├─ jsr FlushGameplayPpuUpdates, ReadNearbyNametableTiles, UpdatePalettes ├─ Scroll from ScrollX/ScrollY ├─ PPUCTRL from PpuCtrlShadow/CocktailModeFlag/ActivePlayerFlag ├─ Controller strobe + 8-bit read (JOY1/JOY2) → Player1Input/Player2Input ├─ Select active input → ActiveInput ├─ inc FrameCounter (frame counter) ├─ GameplaySuspended=0 → jsr RunLevelScript (audio script) └─ Restore, rti ``` --- ## 9. Naming and Residue **Pass 1 complete** — 4,511 lines, canonical audit comments, 19.6% code (1,549 audited instruction lines covering 3,058 code bytes, plus 49 `jsr`/`jmp` call sites). 16 seeded routine entries from table references (same as Tengen variant). **Pass 2 complete** — all 194 address-derived `Label_XXXX` definitions replaced with semantic names (routine entries + `__` internal branch targets). No `Label_` remains. All 49 `jsr`/`jmp` call sites resolved from raw `$XXXX` to labels. Zero raw `$XXXX` call operands remain. **Pass 3 partial** — 15 proven semantic zero-page aliases added to `locals.inc` (GameState, NmiFlag, ScrollX/Y, PpuCtrlShadow, OamDmaPage, CocktailModeFlag, ActivePlayerFlag, GameplaySuspended, FrameCounter, StateTimer, Player1/2Input, ActiveInput, CopyrightVerified) and substituted into `prg.s`. **119 operands** substituted in total: 101 hardware registers, 11 single-alias zero-page, 7 multi-alias resolved per site via `sites.tsv` — `apply_symbols.py` now reports **0 substitutions pending and 0 residue**. Remaining zero-page slots retain neutral `ZpXX` names. **Pass 3 data tables done** - every `; ---- data ----` block in the PRG is now interpreted and named in `prg.s`; the full inventory (address, size, name, record structure and the consuming instruction, or the ROM provenance for the blocks nothing reads) is in [§11. Pass 3 data-table inventory](#11-pass-3-data-table-inventory). 58 tables were named (`ProgrammerSignatureString` at `$C0EB`, `GameStateDispatchTable` at `$C253`, `MazeScreenTileData` at `$C2FD`, `EntityBehaviourHandlerTable` at `$D912`, `AudioCommandHandlerTable` at `$EFDD` and the rest of the list); the blocks that no instruction of this dump reads are recorded in the same table as unreferenced data or as code the tracer could not reach. **Multi-alias resolution (`sites.tsv`)** — 7 zero-page addresses carry more than one name in `locals.inc`; each site was resolved from its instruction context, not guessed: | Sites | Address | Chosen | Why | |---|---|---|---| | $C250, $D3CA, $D90F, $D9B6 | `$0010` | `DispatchPtrLo` | each `jmp ($0010)` is immediately preceded by `sta DispatchPtrLo` / `sta DispatchPtrHi` loading the dispatch-table entry's address | | $EFDA | `$00FE` | `AudioHandlerPtrLo` | preceded by `sta AudioHandlerPtrLo` / `sta AudioHandlerPtrHi`, loaded from `AudioCommandTable` ($EFDD) | | $C048, $C089 | `$0000` | `Zp00` | the literal zero-page base in the two zero-page clear loops — the page base, not a pointer variable, so the neutral name is used rather than the semantic `WorkPtrLo` | The `a:` prefix survives the substitution, so `sta a:Zp00,y` still assembles to `99 00 00` (`sta abs,Y`) and not `91 00` (`sta zp,Y`). **Indirect dispatch** (not followed statically): - `jmp (DispatchPtrLo)` at $C250, $D90F, $D9B6, $D3CA - `jmp (AudioHandlerPtrLo)` at $EFDA **Unusual RESET vector** — Points to $FF00, which contains real boot code (sei/cld, PPU off, double vblank wait, then `jmp ColdStart`). This is valid in NROM since the full $C000–$FFFF bank is mapped — the vector simply doesn't point at the typical $C000+ entry. **Audit comment alignment** — re-done after the Pass 3 substitutions with the new `_tools/align_audit_comments.py`: the emitter pads every instruction to 31 characters so the audit comment starts at column 35, and a substitution that lengthens an operand pushes the comment right. The tool restores the column; it is whitespace-only, so the byte list in every comment still matches `prg.bin` at the stated PRG offset — all 2,387 audit comments checked (1,549 instruction lines covering 3,058 code bytes, 838 data lines covering 13,179 bytes, and the six vector bytes), 0 mismatches. --- ## 10. Promotion to `NROM/` (2026-10-20) Moved from `_wip/NROM/Pac-Man (U)` with `git mv`; the build now compares the **whole ROM**, not just the PRG: - `header.s` / `chr.s` added (one-line `.incbin`s of `header.bin` / `chr.bin`) - `linker.cfg` gained the `HEADER` ($0000, $0010) and `CHR` ($0000, $2000) areas ahead of `PRG` ($C000, $4000) - `rom.nes` — symlink to `Pac-Man (U) [!].nes` (the original ROM name contains a space and brackets) - `Makefile` builds `header.o` / `prg.o` / `chr.o` into `reconstructed.nes` and compares it against `rom.nes` `reconstructed.nes` is 24,592 bytes, MD5 `a69d310f8cae9397fbd39f204672b5c7` — equal to the original ROM. The `header.bin` / `prg.bin` / `chr.bin` splits were checked against the original before promotion: they are the ROM's first 16 bytes, its next 16,384 bytes and its last 8,192 bytes, exactly. Build verification: `make` in `NROM/Pac-Man (U)/` produces `reconstructed.nes`, compared against `rom.nes` (**whole-ROM byte-identical**). ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. **RESET** is at `$FF00` (`Reset`). The CPU comes here out of reset with the PPU and APU in their power-on state and the zero page and RAM uninitialised. The reset path calls: | Address | Routine | |---------|---------| | `$FF0A` | `Reset_WaitVBlankClear_FF0A` (falls through) | | `$FF0F` | `Reset_WaitVBlankSet_FF0F` (falls through) | **NMI** is at `$C0FA` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| **IRQ** is at `$C16A` (`IrqHandler`) - a different address from RESET. --- ## 11. Pass 3 data-table inventory Every `; ---- data $XXXX-$YYYY ----` block in `prg.s` is listed here, split further where one block holds more than one table. "Read by" gives the instruction in this dump that fetches the bytes, by address; where the address is written as "none" the block is not fetched by any instruction of this disassembly and the row records its ROM provenance instead. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000-$C02F` | 48 | `CopyrightText` | ASCII "COPY RIGHT 1984 1980 NAMCO LTD. ALL RIGHTS RESERVED", no terminator | none - credit text only; nothing in the dump reads it | | `$C030-$C041` | 18 | *(unnamed)* | 18 bytes that decode as instructions: `$C033` is `CLD`, then `LDA #$00` / `STA $2000` / `STA $2001` / `LDA $2002` / `BPL *-5`. A reset stub the tracer never reached, because the RESET vector points at `$FF00` | none (unreached code) | | `$C0EB-$C0F9` | 15 | `ProgrammerSignatureString` | ASCII "HIROKI AOYAGI" followed by the sentinel `$AA $55` - the programmer's name | `lda $C0EB,y` at `$C077` (compared byte-for-byte with `$0052,y` for 15 bytes) and `lda $C0EB,y` at `$C091` (copied into `$0052`) | | `$C253-$C258` | 6 | `GameStateDispatchTable` | 3 x 16-bit little-endian code pointers (`$C259`, `$C45A`, `$C4F5`) | `lda $C253,y` at `$C246` and `lda $C254,y` at `$C24B`, index = game state `Zp3F`; the pair is loaded into `Zp10`/`Zp11` and reached by `jmp (Zp10)` at `$C250` | | `$C2FD-$C386` | 138 | `MazeScreenTileData` | 6 rows x 23 CHR tile bytes that make up the maze screen | `lda $C2FD,y` at `$C29C` - 23 bytes per row are written to `PPUDATA` with `PPUADDR` starting at `$20E5`, six rows in all | | `$C387-$C39E` | 24 | `AttractModeVramTextList` | one VRAM write list: address `$2065` then 20 tile bytes, `$FF` ends the list | `lda $C387,y` at `$C2C1`, `$C2C8` and `$C2CF` - (address high, address low, tile...) written to `PPUADDR`/`PPUDATA` | | `$C39F-$C431` | 147 | *(part of `AttractModeVramTextList`)* | ASCII strings terminated by `$FF`: `" 1 PLAYER"`, `"L2 PLAYERS"`, `"©TM AND ] 1980 1984 NAMCO LTD["`, `"©TENGEN"`, `"#&LICENSED BY NINTENDO"`, `"#iOF AMERICA INC["`, ... (the `$ED` byte is the copyright tile) | the same list as above | | `$C412-$C431` | 32 | `InitialPaletteData` | 32 palette bytes: 16 for `$3F00` (background) and 16 for `$3F10` (sprites) | `lda $C412,y` at `$C201` - copied into the palette buffer `$0299` | | `$C432-$C459` | 40 | `NametableAttributeTable` | 40 attribute bytes for VRAM `$23C8-$23EF` (the attribute table of the second nametable) | `lda $C432,y` at `$C2F1` - 40 bytes written to `PPUDATA` after `PPUADDR` is set to `$23C8` | | `$C4EA-$C4F1` | 8 | `TitleScreenVramList` | VRAM write list: address `$220A` with data `$5C $00`, then address `$224A` with data `$20`; `$FF` terminates | `lda $C4EA,y` at `$C462` - copied into the VRAM write buffer `$024B` up to the `$FF` | | `$C4F2-$C55F` | 110 | *(unnamed)* | decodes as instructions (a routine that sets `Zp3F` and enters the dispatcher) | none (unreached code) | | `$C582-$C588` | 7 | *(unnamed)* | `LDA #$01` / `STA Zp47` / `JMP $CA13` | none (unreached code) | | `$C5BB-$C63A` | 128 | *(unnamed)* | decodes as instructions (a PPU initialisation routine) | none (unreached code) | | `$C63B-$C649` | 15 | *(unnamed)* | `LDA #$3F` / `STA $2006` x5 / `RTS` - sets `PPUADDR` to `$3F00` | none (unreached code) | | `$C64A-$C699` | 80 | *(part of `AttractModeVramTextList`)* | a nametable tile stream in which `$0F` is the blank tile and the rest are maze tiles; `$7E` ends it | none in this dump - the same list format as `$C4EA` | | `$C66A-$C6E8` | 127 | `ScreenTextPointerTable` | 35 x 16-bit pointers into the string data that follows at `$C6E9` | `lda $C66A,y` at `$C58B` and `lda $C66B,y` at `$C590`, index = `Zp87`; the pair is loaded into `Zp00`/`Zp01` and the text is copied with `lda (Zp00),y` at `$C597` | | `$C6E9-$C728` | 64 | `GhostSpriteOamTemplateTable` | 16 x 4-byte records (Y, attribute, tile, X) for the ghosts shown on the title screen | `lda $C6E9,y` at `$C5B0` - copied into the OAM shadow `$0760` | | `$C729-$C89A` | 370 | *(part of `AttractModeVramTextList`)* | more VRAM write lists (the "CHARACTER" and ghost-name screens) followed by unreachable code | none in this dump | | `$C997-$C9A9` | 19 | `CharacterSelectDashVramList` | VRAM write list: address `$22AD` followed by eight `$2D` ('-') tiles and a `$00`, then address `$22ED` with six `$2D` tiles and `$FF` | none in this dump - same list format as `$C4EA` | | `$C9AD-$C9B8` | 12 | `GhostTargetAddressTable` | three groups of four bytes, one byte per ghost | `lda $C9AD,y` at `$C8D7` - copied into the per-ghost target array `$028D-$0290`; `lda $C9B1,y` at `$C98A` - four bytes copied into the VRAM write buffer `$024B` | | `$C9FF-$CA12` | 20 | `GhostTargetTileTable` | 20 eight-bit maze tile indices: 0-7 the scatter targets, 8-15 the chase targets, 16-19 the return values | `lda $C9FF,y` at `$C9CD` (index = (animation timer & 7) + entity index, result stored to `$028C`) and at `$C9E8` (index = base + 8, result copied to `$028D-$0290`) | | `$CA96-$CAA7` | 18 | `GameModeHandlerTable` | 9 x 16-bit code pointers | `lda $CA96,y` at `$CA89` and `lda $CA97,y` at `$CA8E`, index = `Zp3F`; `jmp (Zp10)` at `$CA93` | | `$CB1A-$CB25` | 12 | `PauseTextStrings` | two `$FF`-terminated ASCII strings, "PAUSE" and "-----" | `lda $CB1A,x` at `$CAE1` - six bytes copied into the VRAM write buffer `$024D` | | `$CC5B-$CC8E` | 52 | `GhostSpriteShapeData` | 18 bytes of top-row tile indices at `$CC5B`, 16 bytes of bottom-row tile indices at `$CC6D`, a 16-bit pointer at `$CC7F` and a 3-byte row-count table at `$CC85` | `lda $CC5B,x` at `$CB5E`, `lda $CC6D,x` at `$CB64`, `lda $CC7F,x`/`lda $CC80,x` at `$CB47`/`$CB4C`, `lda $CC85,x` at `$CB53` | | `$CC90-$CC9A` | 11 | *(part of `GhostSpriteShapeData`)* | 4 x 16-bit PPU addresses of the ghost eye patterns | `lda $CC90,y` at `$CBF1` and `lda $CC91,y` at `$CBF9`, index = (ghost count - 1) x 2 | | `$CC9B-$CCC4` | 42 | *(unnamed)* | decodes as instructions | none (unreached code) | | `$CDDE-$CE7D` | 160 | *(unnamed)* | decodes as instructions (a routine that clears RAM) | none (unreached code) | | `$CE7E-$CEB1` | 52 | *(part of `GhostSpriteShapeData`)* | 18 bytes of tile indices, eight zero bytes and 12 bytes of palette/attribute data | none in this dump | | `$D0B2-$D0BD` | 12 | `FruitSpawnCounterTable` | 12 bytes copied to `$0032-$003D` | `lda $D0B2,y` at `$D03C` - 12 bytes copied to `Zp32` and up | | `$D0BE-$D0C6` | 9 | `BonusItemVramList` | VRAM write list: address `$2136` then seven tile bytes | `lda $D0BE,y` at `$D02E` - nine bytes copied into the VRAM address buffer `$026B` | | `$D0C7-$D0CF` | 9 | *(part of `BonusItemVramList`)* | nine further tile bytes | none in this dump | | `$D0D0-$D0EF` | 32 | `AlternatePaletteData` | 32 palette bytes (16 background + 16 sprite) | `lda $D0D0,y` at `$CED8` - copied into the palette buffer `$0299` | | `$D11F-$D15E` | 64 | `MazeNametableTileData` | 2 rows x 32 CHR tile bytes; `$55` is the wall tile, `$11` the corridor and `$50`/`$51` the ghost-house door | `lda $D11F,y` at `$D101` - 64 bytes written to `PPUDATA` | | `$D262` | 1 | *(unnamed)* | one byte, `$2B` | none | | `$D32B-$D34A` | 32 | `FruitDefinitionTable` | per-fruit data: the tile index at `$D32B`, the spawn-point X at `$D337`, the spawn-point Y at `$D33B`, and two further per-fruit byte tables at `$D33F` and `$D347` | `lda $D32B,y` at `$D2CE`, `lda $D33B,y` at `$D2DC`, `lda $D337,y` at `$D2E1`, `lda $D33F,y` at `$D319`, `lda $D347,y` at `$D31E`, `lda $D32F,y` at `$D323` | | `$D3CD-$D3D4` | 8 | `GhostMoveHandlerTable` | 4 x 16-bit code pointers | `lda $D3CD,y` at `$D3C0` and `lda $D3CE,y` at `$D3C5`; `jmp (Zp10)` at `$D3CA` | | `$D419-$D420` | 8 | `GhostTurnHandlerTable` | 4 x 16-bit code pointers | `lda $D419,y` at `$D40C` and `lda $D41A,y` at `$D411`; `jmp (Zp10)` at `$D416` | | `$D4C1-$D4C4` | 4 | `GhostDirectionOrderTable` | 4 bytes: the order in which the four ghosts are tried when the player changes direction | `lda $D4C1,x` at `$D362` | | `$D4DE-$D515` | 56 | `PacManAnimationTable` | 28 x 2-byte records (frame duration, direction) | `lda $D4DE,y` at `$D4D1` (the duration) and `lda $D4DF,y` at `$D4D6` (the direction, masked with 3) | | `$D555-$D55E` | 10 | `GhostBehaviourHandlerTable` | 5 x 16-bit code pointers | `lda $D555,y` at `$D548` and `lda $D556,y` at `$D54D`; `jmp (Zp10)` at `$D552` | | `$D55F` | 1 | *(unnamed)* | `RTS` | none (unreached code) | | `$D574-$D57B` | 8 | `GhostTurnDecisionHandlerTable` | 4 x 16-bit code pointers | `lda $D574,y` at `$D567` and `lda $D575,y` at `$D56C`; `jmp (Zp10)` at `$D571` | | `$D5C0-$D5C7` | 8 | `GhostSpeedHandlerTable` | 4 x 16-bit code pointers | `lda $D5C0,y` at `$D5B3` and `lda $D5C1,y` at `$D5B8`; `jmp (Zp10)` at `$D5BD` | | `$D686-$D68D` | 8 | `GhostDeltaVectorTable` | 4 x 2-byte signed (dx, dy) step vectors | `lda $D686,y` at `$D679` and `lda $D687,y` at `$D67E` -> `ZpCD`/`ZpCE` | | `$D69D-$D6A4` | 8 | `GhostFrightenedHandlerTable` | 2 x 16-bit code pointers | `lda $D69D,y` at `$D690` and `lda $D69E,y` at `$D695`; `jmp (Zp10)` at `$D69A` | | `$D6A5-$D6C8` | 36 | *(unnamed)* | decodes as instructions | none (unreached code) | | `$D71B-$D722` | 8 | `GhostTurnOffsetTable` | 4 x 2-byte signed (dx, dy) turn offsets | `lda $D71B,y` at `$D708` and `lda $D71C,y` at `$D710` | | `$D830-$D83F` | 16 | `GhostTurnPriorityTileTable` | 4 groups of 4 bytes: the tile numbers the ghost prefers, in priority order | `lda $D830,y` at `$D7A4` - compared against the four candidate tiles at `$000C-$000F` | | `$D840-$D841` | 2 | `GhostPreferredTilePairTable` | one 16-bit value per ghost (the preferred direction) | `lda $D840,y` at `$D73D` and `lda $D841,y` at `$D742` | | `$D8AB-$D8AE` | 4 | `GhostReverseHandlerTable` | 2 x 16-bit code pointers | `lda $D8AB,y` at `$D89E` and `lda $D8AC,y` at `$D8A3`; `jmp (Zp10)` at `$D8A8` | | `$D912-$D91B` | 10 | `EntityBehaviourHandlerTable` | 5 x 16-bit code pointers | `lda $D912,y` at `$D905` and `lda $D913,y` at `$D90A`; `jmp (Zp10)` at `$D90F` | | `$D91C` | 1 | *(unnamed)* | `RTS` | none (unreached code) | | `$D985-$D98A` | 6 | `FruitScoreOffsetTable` | 6 bytes: the per-fruit score offsets added to the base score | `lda $D985,y` at `$D97C` | | `$D9B9-$D9C2` | 10 | `EntitySpriteHandlerTable` | 5 x 16-bit code pointers | `lda $D9B9,y` at `$D9AC` and `lda $D9BA,y` at `$D9B1`; `jmp (Zp10)` at `$D9B6` | | `$DBAD-$DCC8` | 284 | `CharacterSpriteTileTable` | the CHR tile indices of the five characters (Pac-Man's mouth animation and the four ghosts), 8 bytes per animation frame, with `$4C` marking an unused cell | `lda $DBAD,y` at `$DB1B` and `lda $DCAD,y` at `$DB3E` - the tile goes into the OAM buffer | | `$DCC9-$DE14` | 332 | *(part of `CharacterSpriteTileTable`)* | the per-character attribute bytes and the `$FF` fill of the unused OAM cells | `ora $DCE1,y` at `$DB29`, `ora $DDE1,y` at `$DB4C` | | `$DE15-$DE1C` | 8 | `GhostOamAttributeTable` | 4 x 2-byte (tile, attribute) records, one per ghost | `adc $DE15,x` at `$DAD4`, `ora $DE15,y` at `$DB06` and `$DB6F`, `adc $DE16,x` at `$DB81` | | `$DF2B-$DF3A` | 16 | `GhostEyeOffsetTable` | 8 x 2-byte (x, y) offsets of the ghost's eyes | `lda $DF2B,y` at `$DEE9` and `lda $DF2C,y` at `$DEEF` - written to `PPUADDR` | | `$E002-$E003` | 2 | `FruitSpriteDescriptorTable` | the tile and the repeat count of the bonus item that appears | `lda $E002,x` at `$DFAA` and `lda $E003,x` at `$DFBB` | | `$E004-$E019` | 22 | *(unnamed)* | decodes as instructions | none (unreached code) | | `$E01A-$E021` | 8 | `BonusFruitTileTable` | the CHR tile of each of the eight bonus fruits (`$24` ... `$2B`) | `lda $E01A,y` at `$DFE4` | | `$E17F-$E18A` | 12 | `DigitTileVramList` | 4 x 3-byte records (VRAM address high, address low, tile); the tile is written four times to draw a doubled digit | `lda $E17F,x` at `$E12B`, `$E136` and `$E145` | | `$E341-$E343` | 3 | `ScreenFillTileTable` | the tile used to fill a run of eight cells | `lda $E341,y` at `$E327` - written to `PPUDATA` eight times | | `$E45E-$E46F` | 18 | `SpriteDataPointerTable` | 9 x 16-bit values (`$0061`, `$0070`, `$0080`, ...) used as an indirect pointer into RAM | `lda $E45E,x` at `$E406` and `lda $E45F,x` at `$E40B` -> `Zp02`/`Zp03`, then `lda (Zp02),y` at `$E435` | | `$E4FB-$E511` | 23 | `PlayerSelectTextVramList` | 3 VRAM writes: (address `$2076`, 8 tiles `$B4`...`$BB`), (address `$20F7`, 3 tiles), (address `$2177`, 3 tiles) | `lda $E4FB,y` at `$E4C5`, `$E4D2`, `$E4D9` and `$E4DF` | | `$E659-$E694` | 60 | `ScoreDigitGlyphData` | three tables: 13 bytes of digit-row indices at `$E659`, 8 x 16-bit digit counts at `$E66D`, 24 bytes of 4-bit digit row patterns at `$E67D` | `lda $E659,x` at `$E5D2`, `lda $E66D,y`/`lda $E66E,y` at `$E5E1`/`$E5E6`, `lda $E67D,y` at `$E5ED` | | `$E695-$E9D7` | 837 | *(unnamed)* | decodes as instructions (the score drawing and the game-state routines) | none (unreached code) | | `$EA62-$EA7B` | 26 | `FruitSpriteHandlerTable` | 3 x 16-bit code pointers | `lda $EA62,y` at `$EA55` and `lda $EA63,y` at `$EA5A`; `jmp (Zp10)` at `$EA5F` | | `$EA7C-$EB5D` | 226 | *(unnamed)* | decodes as instructions | none (unreached code) | | `$EB75-$EBF4` | 128 | `LevelGhostConfigIndexTable` | 8 entries, each the index of one of the 33 per-level records in `$EBFF` | `lda $EB75,x` at `$CF27`, `$CF45`, `$CF65`, `$CF6E` and `$CFAA` | | `$EBF5-$EBFE` | 10 | *(part of `LevelGhostConfigIndexTable`)* | 5 x 16-bit values (`$00D0`, `$00C0`, ...) | none in this dump | | `$EBFF-$EE4A` | 332 | `LevelGhostTargetTable` | 33 records x 22 bytes of per-level ghost configuration | `lda $EBFF,y` at `$CF38` - 22 bytes copied to `Zp9F` and up | | `$EC6D-$EC8C` | 32 | `LevelGhostDirectionTable` | 4 records x 8 bytes of per-level ghost direction data | `lda $EC6D,x` at `$CF50` and `$CF55` | | `$EC8D-$EC9A` | 14 | `LevelGhostSpeedTable` | per-level ghost speed data | `lda $EC8D,x` at `$CF73` and `$CF79` | | `$EC9B-$EE4A` | 176 | `LevelGhostTileTable` | the per-level ghost tile and pattern data | `lda $EC9B,x` at `$CF88`, `$CF9A` and `$CFB2` | | `$EFDD-$EFFC` | 32 | `AudioCommandHandlerTable` | 16 x 16-bit code pointers - one handler per sound command | `lda $EFDD,x` at `$EFD0` and `lda $EFDE,x` at `$EFD5`; `jmp (ZpFE)` at `$EFDA` | | `$F0A7-$F0BC` | 24 | `AudioNotePeriodTable` | 12 x 16-bit APU period values - one octave of the note table; each value is 2^(1/12) times the one before it | `lda $F0A7,x` / `lda $F0A8,x` at `$EF73`/`$EF78`, index = (command & $F0) >> 4 | | `$F0BF-$F0C0` | 2 | `AudioSequenceDataPointer` | a 16-bit pointer to the audio sequence data, followed by the first byte of the code that follows | `lda $F0BF` at `$EE5B` and `lda $F0C0` at `$EE60` -> `ZpF4`/`ZpF5` | | `$F0C1-$FEFF` | 3647 | *(unnamed)* | a mixture of traced code and unreached code (the sound engine and the level scripts) | none as a whole | | `$FF17-$FFF7` | 225 | *(unnamed)* | `$FF` padding filling the rest of the PRG image | none | | `$FFF8-$FFF9` | 2 | *(unnamed)* | the 16-bit pointer `$ABFF` - the start of the packed tile/attribute stream that the routine at `$E2B3` unpacks | `lda $FFF9` at `$E2B3` and `lda $FFF8` at `$E2B8` | | `$FFFA-$FFFF` | 6 | *(unnamed)* | the three 6502 vectors: NMI `$C0FA`, RESET `$FF00`, IRQ `$C16A` | the CPU's hardware | ### Notes on the interpretation * The VRAM write lists (`$C387`, `$C4EA`, `$C997`, `$E4FB`, `$E17F`) all share one format: a VRAM address (two bytes) followed by the tile bytes to write there, ended by `$FF`. The routine at `$C2BE` walks the `$C387` list and the routine at `$DF07` walks any list copied into `$024B`, so the same format is used by both. * The `$E659` score table is the one place where the exact meaning of the bytes is not fully established: the 13 bytes at `$E659` select a digit row, the 8 16-bit values at `$E66D` give a cell offset and a repeat count, and the 24 bytes at `$E67D` are 4-bit-wide row patterns for the drawn digit. The three parts are separated by their own consumers, so they are labelled separately, but the digits they draw are not identified. * `$D32B` (fruit) and `$D4DE` (Pac-Man's animation) are likewise read by several instructions each; the per-fruit and per-frame record sizes above are the ones the reading code implies, not a claim about every byte. --- ## 12. Naming residue — address-derived entry points Every `jsr`/`jmp` target in this project now carries a semantic name except **13**, which keep the honest address-derived `Sub_XXXX` form because the dump does not reveal what they are for: | Label | Address | Why the dump does not settle the name | |--------|---------|-------------------------------------| | `Sub_D06A` | `$D06A` | Writes ten rows of six blank tiles and two `-` tiles into VRAM at `$2256` (`$222A` in cocktail mode). The screen that owns that VRAM block is not identified by anything in this dump, so "the screen that draws dashes" would only rename the code, not say what it is for. | | `Sub_D263` | `$D263` | Reads the entity state array at `$00B8` and compares the entity's coordinates with `$001E`. The entity role (Pac-Man, ghost, fruit) is not distinguishable from the instructions alone. | | `Sub_D91D` | `$D91D` | Scans the entity state array for the entry whose state is `$06` and stores its index at `$0608`. Which entity carries state `$06` is not established by the dump. | | `Sub_DA19` | `$DA19` | Compares the position at `$0274` with the position at `($00)`,y and computes the signed difference; the caller treats it as a distance test but the code does not name the two things being compared. | | `Sub_DE1D` | `$DE1D` | Runs only every sixteenth frame (`$004B & $0F`) and then copies a nibble into `$006C`. The period and the field it feeds are not identified. | | `Sub_DF3B` | `$DF3B` | Dispatches on the byte at `$022A` to four different continuations; the dump does not show what `$022A` counts. | | `Sub_E022` | `$E022` | Sets the three counters `$0088`, `$0089`, `$008A` to `$0F`, `$00`, `$1E`; the dump does not say what those counters measure. | | `Sub_E04D` | `$E04D` | Sets up the pair `$002F/$0002` and `$001E` and then tests the entity state array for state `$04`; the role of the entity in state `$04` is not established. | | `Sub_E3BE` | `$E3BE` | Points the score writer at VRAM `$20B6` (or `$28B6` in cocktail mode) with a stride of `$80` and then falls into the shared "write N score numbers" loop. Whether that block is the 1UP score, the 2UP score or the high score is not settled by the dump. | | `Sub_E3D8` | `$E3D8` | The same score writer, pointed at VRAM `$2083` with a stride of `$09` and at the player-2 score variable. The dump does not identify the screen row the two score blocks occupy. | | `Sub_E512` | `$E512` | Clamps a value to seven and then draws a block of doubled tiles at VRAM `$2317` (or `$231F` in cocktail mode). The value it clamps is a fruit/level index in some form, but the dump does not say which. | | `Sub_E580` | `$E580` | Prepares the digit fields (`$000A`, `$000B`, `$000C`, `$000D`) and clamps the digit count to `$0C` before the score writer runs. The digits it describes are not identified. | | `Sub_E9D8` | `$E9D8` | Adds the 16-bit word at `$001A` to the one at `$0274` and then tests the high byte against `$C0`. The two words are positions of some pair of entities, but which is not established. | All thirteen are reachable entry points whose behaviour the instructions do show; they are kept address-derived rather than given a name that would assert more than the ROM proves.