# Pac-Man (USA) (Namco) — Game Flow from Power-On This document describes the execution flow of the disassembled Pac-Man (USA) (Namco) 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`. --- ## 1. ROM and Memory Layout | Property | Value | |----------|-------| | Format | iNES / NES 2.0 | | PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) | | CHR | 1 × 8 KB | | Mirroring | Horizontal | | PRG bank start | CPU $C000 | | Source file | `copilot/prg.s` (14,332 lines) | | Output | `reconstructed.nes` (whole ROM: 16-byte header + 16,384-byte PRG + 8,192-byte CHR) | ### Interrupt vectors (CPU $FFFA–$FFFF) | Vector | Address | Handler | |--------|---------|---------| | NMI | $C10E | `NmiHandler` | | RESET | $C033 | `Reset` | | IRQ / BRK | $C17E | `IrqHandler` (just `rti` — unused) | Additional ROM data at $FFF6–$FFF9: - $FFF6/$FFF7 = $ECEC (little-endian), used by `DrawMazeNametable` as a source pointer ($00/$01) - $FFF8/$FFF9 = $C10E (little-endian) --- ## 2. Build Status `make` in `copilot/` assembles `prg.s` with `ca65`, links with `ld65`, and verifies against `prg.bin`. **Verification result: BYTE-IDENTICAL** — `reconstructed.nes` matches `rom.nes` exactly (header + PRG + CHR). --- ## 3. Reset — CPU $FFFC → $C033 **`Reset`** ($C033) On power-on or reset: 1. `ldx #$00 / stx PPUCTRL` — disable PPU rendering/NMI. 2. `sei / cld` — disable maskable interrupts, clear decimal mode. 3. `dex / txs` — initialize stack pointer to $FF. 4. `nop x3` — three NOP padding bytes. 5. **Wait for two VBlanks** — `@WaitForPpu` loop: set PPUMASK=$06, poll PPUSTATUS bit 7 twice (X counts down from 2). This is the PPU warm-up delay. 6. **Clear low RAM** — `@ClearLowRam`: zero $0000–$003D. 7. **Clear RAM pages** — `@ClearRamPages`: using indirect pointer at $00/$01, clear pages starting at $0100 upward until high byte reaches $08 (effectively clearing $0100–$07FF). 8. `PPUMASK=$06`, scroll=$00 — enable background + sprites, reset scroll. 9. **Warm-start signature check** — `@CheckRamSignature`: compare RAM at $0052 against the 15-byte string `"HIROKI AOYAGI"` plus `$AA,$55` (stored at `RamSignature`, $C354–$C366): - If all 15 bytes match → warm start: skip full RAM clear, go to `@SignatureAlreadyPresent`. - If any byte differs → cold start: `@InitializeRamSignature` clears all of zero page, then copies the signature string to $0052–$0060. 10. Set $64=$01 (some flag), set gameState=$00. 11. Set $45=$07, then check cocktail/active-player flags at $46/$47. If both nonzero, perform a 16-byte swap loop (`@SwapLoop`) swapping bytes between $67,y and $77,y for Y=0..15. 12. `APUCTRL=$1F`, `APUFRAMECNT=$C0` — enable all APU channels, set frame counter mode. 13. `jsr InitAudio` ($EE8C) — set up the audio engine's pointers and workspace. 14. `jsr ClearAudioWorkspace` ($EEB4) — clear the 16 bytes of audio workspace. 15. Set PPUCTRL=$88, `ppuCtrlShadow`=$88 — enable NMI, $2000 nametable base. 16. Set $48=$FF (gameplay suspended flag), clear $023F and $024B (PPU update buffers). 17. Set $36=$01 (some state flag). 18. `jmp MainEntry` — transfer control to the main program. **`RamSignature`** ($C354): `.byte "HIROKI AOYAGI", $AA, $55` — developer credit / warm-start validation signature. --- ## 4. NMI / Frame Heartbeat — `NmiHandler` ($C10E) The NMI fires once per frame and is the central real-time driver: 1. **Save registers** (A, X, Y on stack). 2. **PPUMASK=$1E** — enable background + sprites with specific emphasis settings. 3. **OAM DMA**: write $00 to OAMADDR ($2003), $00 to $40, then `lda $45 / sta OAMDMA` ($4014) — DMA from page $45 (i.e., $4500–$45FF) to sprite RAM. 4. **`jsr FlushGameplayPpuUpdates`** ($DE3B region) — flush pending PPU gameplay update blocks at $023F and $0245. 5. **`jsr ReadNearbyNametableTiles`** — read nametable tile data around Pac-Man (likely for collision/tunnel logic). 6. **`jsr UpdatePalettes`** — upload palette data from RAM buffers $0299 and $02A9 to PPU palette $3F00/$3F10 when those buffers are marked active ($0F marker). 7. **Restore scroll**: write $41 and $42 to PPUSCROLL (two writes — horizontal then vertical). 8. **Construct PPUCTRL**: `lda $47 / and $46 / and #$01 / asl / ora $43` — builds the PPUCTRL value from shadow/flag bits, writes to PPUCTRL. 9. **Controller strobe**: write $01 then $00 to JOY1 ($4016). 10. **Read both controllers** — 8 iterations each: - Controller 1: read JOY1, mask with #$03, compare with #$01, rotate into $4D (`currentInput`). - Controller 2: read `APUFRAMECNT` ($4017), mask with #$03, compare with #$01, rotate into $4E. 11. **Select active controller**: X = `currentInput` ($4D) by default; if `cocktailModeFlag` ($46) & `activePlayerFlag` ($47) both nonzero, X = `newlyPressedInput` ($4E). Store X to `previousInput` ($4F). 12. **`inc frameCounter`** ($4B) — increment per-frame counter. 13. **$48 gate**: if $48 (`gameplaySuspended`) != 0, skip to `@NmiDone`. 14. **`jsr RunLevelScript`** ($EED0) — scan and activate the audio channel slots. 15. **`@NmiDone`**: restore registers (Y, X, A), `rti`. The label `IrqHandler` appears at the `rti` following NMI's register restore — it is the same code address, just an alternate entry label for the IRQ vector (which points to the same `rti`). ### `UpdatePalettes` Conditionally uploads 16-byte palette blocks from RAM $0299 (to PPU $3F00) and $02A9 (to PPU $3F10) when the corresponding buffer marker byte equals $0F. After upload, resets the marker and PPU address to $3000. --- ## 5. Main Entry — `MainEntry` ($C573) After Reset, control transfers to `MainEntry`. This is the foreground state-machine entry: 1. **`$40=$01`** — set the NMI sync flag (NMI will clear it each frame). 2. **`@WaitForInitialNmi`** — spin until NMI clears $40 to $00 (synchronize to first VBlank). 3. **PPUCTRL=$88 / ppuCtrlShadow=$88** — enable NMI. 4. **PPUMASK=$00** — disable rendering. 5. **Clear OAM page** — fill $0700–$07FF with 0. 6. `cocktailModeFlag=$00`, then call: - `jsr ClearGameplayNametables` - `jsr WriteMazeAttributeTable` ($C30B) - `jsr DrawMazeGraphics` ($C2A6) - `jsr $E419` — additional init (address-named; see §14). 7. **Initialize palette buffers** — copy 32 bytes from `TitleScreenPalette` to RAM $0299–$02B8. 8. Set `actorStatusDisplay` ($6C–$6F) = $2D, set $023F=$FF, `gameplaySuspended`=$FF. 9. Clear $47, $46, $42, $41, `stateTimer` ($4C), $87, $88. 10. **PPUCTRL selection**: if `gameState` ($3F) != 0, use $8A; else use $8A as well (both paths end up with $8A; the `bne` is effectively a no-op in current code). Write to PPUCTRL and `ppuCtrlShadow`. 11. **`MainDispatchLoop`** ($C645): - `$40=$01`, spin until NMI clears it (`@WaitForNmi`). - `Y = gameState` ($3F), load 16-bit handler address from `MainStateHandlerTable` ($C267) into $10/$11. - `jmp ($0010)` — indirect jump to the state handler. **`MainStateHandlerTable`** ($C267–$C26E) — 4 entries, indexed by `gameState` as a byte offset (even values $00, $02, $04, $06): | Offset | Handler | Description | |--------|---------|-------------| | $00 | `InitialStateHandler` | Title/input-wait state | | $02 | `PlayerSelectionState` | 1P/2P selection | | $04 | `State04Handler` | Substate dispatch machine | | $06 | `TimedStateTransition` | Timed state transition | --- ## 6. Main State Handlers ### `TimedStateTransition` ($C27A) — state offset $06 - Decrement `$0F` countdown. If nonzero, return to `MainDispatchLoop`. - When zero: set `gameState`=$02, return to `MainDispatchLoop`. - This is a timed transitional state: another state places a duration in `$0F` and selects state $06; this handler counts down before advancing to state $02. ### `InitialStateHandler` ($C27A region, actually at $C27A? No — at $C27A is TimedStateTransition; InitialStateHandler is at $C27A? Let me re-check) Actually from the listing: - `TimedStateTransition` is at $C27A - `InitialStateHandler` is at $C27A? No. Let me re-read: Line 730: `TimedStateTransition:` is at $C27A (based on comment "$C27A-$C2A5: Timed State Transition Handler" — wait, the comment says $C27A but the code shows `dec $0F` at line 731. Then line 740: `; $C27A-$C2A5: Initial/Input State Handler` and line 766: `InitialStateHandler:`. So there's a discrepancy in comments. The actual code: - `TimedStateTransition` = `dec $0F / bne MainDispatchLoop / lda #$02 / sta gameState / jmp MainDispatchLoop` — this is state $06's handler. - `InitialStateHandler` = checks controller bits $0C; if set → gameState=$06, $0F=$5A, jump MainEntry. Else increment $42; if $42 reaches $F0 → reset $42, ppuCtrlShadow=$88, gameState=$06, $0F=$5A. Otherwise return to MainDispatchLoop. ### `PlayerSelectionState` ($C546 region) — state offset $02 - If $87|$88 != 0 → `@AdvanceTimeout` (16-bit timeout counter). - Else (first entry): copy `PlayerCountSelectionDisplayData` ($C507–$C50E) to RAM $024B until $FF terminator. Capture controller bits $04 and $08 into `pacmanRequestedDirection` ($50) and `pacmanDirection` ($51). - `@AdvanceTimeout`: increment 16-bit counter at $87/$88. If high byte reaches $02 → reset counter, set gameState=$04, jump MainDispatchLoop. - `@CheckSelectionInput`: if controller bit $04 changed → store as `pacmanRequestedDirection`, reset counter, set $87=$01, toggle `activePlayerFlag` ($47) between 0/1, update display marker tiles at $024D/$0251 from `PlayerCountSelectionMarkerTiles` ($C50F–$C511). - `@CheckConfirmInput`: if controller bit $08 changed → store as `pacmanDirection`. If bit $08 now active → clear `gameplaySuspended`, `jsr ClearAudioWorkspace`, jump `GameModeEntry`. **`PlayerCountSelectionDisplayData`** ($C507): `$22,$0A,$5C,$00, $22,$4A,$20,$FF` — copied to $024B on entry. **`PlayerCountSelectionMarkerTiles`** ($C50F): `$5C,$20,$5C` — selection marker toggle data. ### `State04Handler` ($C52E region) — state offset $04 - If controller bits $0C set → gameState=$02, jump MainEntry (restart). - Else: `Y = $87` (substate offset), load 16-bit handler from `State04SubstateTable` ($C52E), jump indirect via $10/$11. **`State04SubstateTable`** ($C52E) — 12 entries indexed by even offsets $00–$16: | Offset | Handler | |--------|---------| | $00 | `State04Initialize` | | $02–$12 | `State04TimedSubstate` (shared, 6 entries) | | $14 | `State04LongDelay` | | $16 | `State04FinalPhase` | - `State04Initialize` ($C573): sync to NMI via $40, PPUCTRL=$08, disable rendering, clear $88, call `$C5D8`, `InitializeState04PpuData` ($C608), `LoadState04DisplayData` ($C5A6), advance $87 by 2, PPUCTRL=$88, return to loop. - `State04TimedSubstate` ($C589): increment $88; at $30 frames → clear $88, call `$C5A6`, advance $87 by 2. - `State04LongDelay` ($C5A6): increment $88; at $80 frames → advance $87 by 2 to substate $16. - `State04FinalPhase` ($C5D7): sets $47=$01, tail-jumps to `GameModeEntry`. - `SetSelectionFlagAndContinue` ($C5D7): `activePlayerFlag=$01`, `jmp GameModeEntry`. ### `LoadState04DisplayData` ($C5A6) Uses $87 as offset into `CharacterIntroDisplayDataTable` ($C687), copies $FF-terminated stream to RAM $024B. Optional secondary copy of 16 bytes from `CharacterIntroSpriteData` based on substate offset. **`CharacterIntroDisplayDataTable`** ($C687) — 10 pointers to display streams for characters/nicknames (SHADOW, BLINKY, SPEEDY, PINKY, etc.) plus scoring legend. --- ## 7. Game Mode Entry — `GameModeEntry` ($C328) Reached from `SetSelectionFlagAndContinue` (state $04 final phase). This transitions from the attract/selection sequence into gameplay: 1. PPUCTRL=$88, ppuCtrlShadow=$88. 2. Wait for VBlank. 3. PPUMASK=$00, gameState=$00, cocktailModeFlag=$00, $69=$00, $4A=$00. 4. `jsr ClearGameplayNametables`, `jsr DrawGameplayHudLabels`, `jsr DrawGameplayScoreFields`. 5. If `gameplaySuspended` != 0 → roundNumber=$01 (continue existing game). 6. Else → clear 32 bytes starting at roundNumber ($67–$86), set roundNumber=$03, $77=$03, clear $0600/$0601. 7. Common init: roundDisplayValue=$FF, $78=$FF, $0245=$FF, ppuCtrlShadow=$88, PPUCTRL=$88. 8. **`GameModeFrameSync`** ($C360): `$40=$01`, wait for NMI clear ($@WaitForNmi`), then: - If `gameplaySuspended` != 0 AND controller bits $0C set → gameState=$02, jump MainEntry (restart). - If `stateTimer` ($4C) != 0 → decrement, return to `@WaitForNextFrame`. - Else `@DispatchState`: `Y = gameState`, load handler from `GameModeStateTable` ($CAD7) into $10/$11, indirect jump. **`GameModeStateTable`** ($CAD7) — 8 entries indexed by even offsets $00–$0E: | Offset | Handler | |--------|---------| | $00 | `GameModeState00` | | $02 | `GameModeState02` | | $04 | `GameModeState04` | | $06 | `GameModeState06` | | $08 | `GameModeState08` | | $0A | `GameModeState0A` | | $0C | `GameModeState0C` | | $0E | `InitializeGameplayTransitionState` | ### `GameModeState04` ($CAE9) — pause/per-frame handler - If $060F != 0 → return to `GameModeFrameSync`. - Else: edge-detect controller bit $08 using $49 as previous state. On new press → increment $4A, install pause display commands at $024B, toggle pause display via bit 0 of $4A. - When not paused and bit 0 of $4A clear → call gameplay helper chain before returning to `GameModeFrameSync`. - Adjacent data at $CB5B contains tile values for "PAUSE" text — confirms this is the pause-state handler. --- ## 8. PPU / Rendering ### `DrawMazeNametable` ($E2E4) Draws the maze nametable using compressed data: - PPU destination selected based on cocktail mode ($47 & $46): $2000 or $2080. - Source pointer from `MazeDataPointerBytes` at $FFF6–$FFF7 ($ECEC → points to ROM data). - Each row: set PPU address, decode 2-bit-run-length-encoded bytes from the source (2 bits = repeat count-1, 6 bits = tile value), write $16 (22) bytes per row. - Advance PPU destination by $20 per row. ### `FlushGameplayPpuUpdates` ($DE3B) - If $48 (`gameplaySuspended`) == 0 → process update block at $023F (6 bytes + terminator to PPU). - Else → jump to `UpdateAnimatedGameplayPpuFields`. - Block at $0245 processed similarly if nonzero. --- ## 9. Audio System **`InitAudio`** ($EE8C): - Clear audio request pointer ($F0/$F1 = $0006), channel table pointer ($F2/$F3 = $0620), sequence table pointer from ROM $F100/$F101. - audioWorkValue=$40, APUSTATUS=$1F, APUFRAMECNT=$C0. - Clear 16 bytes through ($F0), clear 16 entries at 8-byte stride through ($F2). **`RunLevelScript`** ($EED0): - Clear audio channel flags and work values, scan 16 eight-byte slots through ($F2). - Slots with values $01–$04 activate a channel: copy 4-byte parameters through indirect pointer $F6. - Falls through to runtime command processing at $EF2D. **`UpdateAudioChannels`** ($EF33): - For each active slot: decrement countdown, fetch command byte from sequence table, decode command ($F0+ = special, $C0+ = delay, else = note/frequency), update APU registers ($4000–$400F). **`UpdateAudio`** ($F8DE): - APUFRAMECNT=$C0, update Pulse 1, Pulse 2, Noise, Sound Effect channels. - Clear $FF–$FB; if $07FF != 0 → APUDMALOAD, decrement $07FF. Audio data tables at $FF00–$FFF5 (period/mask tables), $FF64–$FFEA (sequences/instruments), $F450–$F49A (runtime command data). --- ## 10. Zero-Page / RAM Symbols | Address | Symbol | Use | |---------|--------|-----| | $00–$01 | workPtr0/1 | General indirect pointers | | $02–$09 | workValue0–5 | General scratch values | | $0A | pathCompareValue | Maze pathfinding | | $0B | pathFallbackValue | Maze pathfinding | | $0C | legalDirections | Maze pathfinding | | $10 | dispatchVector | State dispatch indirect pointer | | $12–$15 | arithmeticValue/Operand | 16-bit arithmetic | | $1A–$2D | pacmanMotion–ghost4Motion | Actor motion records (4 bytes each) | | $2E–$2F | pacmanTargetRow/Column | Pac-Man maze target | | $32 | pacmanSpriteFrame | Pac-Man sprite frame | | $33 | ghostSpriteFrames | Ghost sprite frames | | $39 | ghostDirectionFlags | Ghost direction/control state | | $3F | gameState | Main gameplay state | | $40 | — | NMI sync flag ($01=pending, NMI clears to $00) | | $41 | ppuCtrlShadow | PPUCTRL mirror | | $42 | — | Second PPUSCROLL write | | $46 | cocktailModeFlag | Cocktail cabinet mode | | $47 | activePlayerFlag | Active player (0/1) | | $48 | gameplaySuspended | Gameplay suspended flag | | $4B | frameCounter | Per-frame counter | | $4C | stateTimer | State countdown timer | | $4D | currentInput | Controller 1 input | | $4E | newlyPressedInput | Newly pressed input | | $4F | previousInput | Previous input state | | $50 | pacmanRequestedDirection | Requested direction | | $51 | pacmanDirection | Current direction | | $61 | highScoreDigits | High score digits | | $67 | roundNumber | Round number | | $68 | roundDisplayValue | Round display value | | $6A | pelletsRemaining | Pellets remaining | | $6B | extraLifeAwarded | Extra life flag | | $6C–$6F | actorStatusDisplay | Actor status display | | $70 | playerScoreDigits | Player score digits | | $F0–$F7 | audioRequestPtr…audioWorkValue | Audio engine pointers/values | | $F8 | audioChannelFlags | Audio channel flags | | $FC | audioRequestIndex | Audio request index | | $FD | audioChannelOffset | Audio channel offset | | $FE–$FF | audioIndirectPtr | Audio indirect pointer | --- ## 11. Data Tables and Pass 3 data-table inventory The PRG is **27.6% code / 72.4% data** (4,525 / 11,859 bytes). Every data block in the PRG is listed below. The names are the labels now in `prg.s`; a `-` means the block is left **unnamed in the source** because no instruction in this dump reads it, or because its layout could not be established from this dump. ### The two PPU-update record formats The game paints its screens with byte streams that are replayed into the PPU. Two formats are in use, and both are decoded from the consuming code. **Title-screen format** (`SubCC2E7`, `$C2E7`): a record is `[PPU address high][PPU address low][tile bytes…][$FF]`. `lda $C3B0,y` at `$C2EA` and `$C2F1` write the two address bytes to `$2006`, the inner loop at `$C2F8`–`$C303` writes tile bytes to `$2007` until it meets `$FF`, and the outer loop at `$C2E1`–`$C308` repeats it **nine** times (`lda #$09 / sta Zp00`). **Character-intro format** (`SubCC5A6`, `$C5A6`): the same shape, but the stream is reached through a pointer table and the copy stops at `$FF`; a `$00` byte after the `$FF` means "this character also has a palette", in which case 16 palette bytes are copied from `$C72E` into `$0760` (`SubCC5CD`). ### Pass 3 data-table inventory | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000–$C032` | 51 B | - | The ASCII string `COPYRIGHT 1980 1993 NAMCO LTD. ALL RIGHTS RESERVED` followed by `$A2` and `$00`, at the very head of the PRG image, immediately before the reset entry at `$C033`. | not referenced by any instruction — the on-screen copyright line is a *different* string, in `TitleScreenPpuUpdateTable` | | `$C0FF–$C10D` | 15 B | `WarmStartSignature` | 13 ASCII bytes `HIROKI AOYAGI` followed by the two bytes `$AA`, `$55`. The reset path compares this 15-byte pattern against the RAM at `$0052`–`$0060`; a match means "warm start", a mismatch means the RAM is cleared and the pattern is written there. | `lda $C0FF,y` at `$C08B` (the comparison loop, `cpy #$0F` at `$C094`) and `lda $C0FF,y` at `$C0A5` (the copy into `$0052`) | | `$C267–$C26E` | 8 B | `MainStateHandlerTable` | Four little-endian 16-bit handler addresses, one per main game state: `$C27A` (state `$00`, the attract loop), `$C477` (state `$02`, player-count selection), `$C512` (state `$04`, the character intro) and `$C26F` (state `$06`). | `lda $C267,y` at `$C25A` and `lda $C268,y` at `$C25F`; the two bytes are combined into `$10`/`$11` and dispatched with `jmp (Zp10)` at `$C264` | | `$C27A–$C2A2` | 41 B | - | **Code the tracer could not reach**: the state-`$00` attract-loop handler. It is only entered through the `jmp (Zp10)` of the handler table, which a static tracer cannot follow. It decodes cleanly as 20 instructions. | reached only through `jmp (Zp10)` at `$C264` | | `$C326–$C3AF` | 138 B | `TitleScreenTileRows` | 6 rows × 23 tile indices. `SubCC2A6` (`$C2A6`) writes them to nametable 0 starting at `$20E5`, adding `$20` to the address after each row, so the rows land at `$20E5`, `$2105`, `$2125`, `$2145`, `$2165` and `$2185`. | `lda $C326,y` at `$C2C5` (23 bytes per row, `dec Zp03` at `$C2CC`) | | `$C3B0–$C43E` | 143 B | `TitleScreenPpuUpdateTable` | Nine PPU-update records in the title-screen format: the PAC-MAN logo at `$2065` (21 tiles), `1 PLAYER` at `$220C`, `2 PLAYERS` at `$224C`, `TM © 1980 1993 NAMCO LTD` at `$22E3`, `NAMCO HOMETEK © INC` at `$2327`, `LICENSED BY NINTENDO` at `$2366`, two tiles at `$2293`, eight tiles at `$22AC` and two tiles at `$20FC`. | `lda $C3B0,y` at `$C2EA`, `$C2F1` and `$C2F8` | | `$C43F–$C45E` | 32 B | `TitleScreenInitialPalette` | The 32-byte master palette image that is copied into the palette buffer at `$0299` when the title screen is set up. | `lda $C43F,y` at `$C215` (32 iterations, `cpy #$20` at `$C21C`), stored to `$0299,y` | | `$C45F–$C476` | 24 B | `TitleScreenAttributeTable` | The 24 attribute bytes of one nametable, written straight to `$23C8`. | `lda $C45F,y` at `$C31A` (24 iterations, `cpy #$18` at `$C321`) | | `$C507–$C50E` | 8 B | `PlayerCountSelectionPpuRecords` | Two PPU-update records for the player-count screen: tile `$5C` at `$220A`, tile `$00` at `$220B`, and tile `$20` at `$224A`, then the `$FF` terminator. | `lda $C507,y` at `$C47F`, copied into the record buffer at `$024B` until the `$FF` at `$C50E` | | `$C50F–$C511` | 3 B | `PlayerCountCursorTiles` | The tile pair `$5C`, `$20` and the duplicate `$5C`: the two cursor tiles and the tile the other player slot is set to. | `lda $C50F,x` at `$C4D5` and `lda $C510,x` at `$C4DB` (X is 0 or 1 depending on which player is highlighted) | | `$C52E–$C545` | 24 B | `State04SubstateHandlerTable` | Twelve little-endian 16-bit addresses: the timed character-introduction substates. `$C546` is the first, `$C573` is used by eight of them, `$C589` by one and `$C792` by the last. | `lda $C52E,y` at `$C521` and `lda $C52F,y` at `$C526`; dispatched with `jmp (Zp10)` at `$C52B` | | `$C573–$C5A5` | 51 B | - | **Code the tracer could not reach**: the state-`$04` substate handler that eight of the table entries point at. Reached only through `jmp (Zp10)` at `$C52B`. | reached only through `jmp (Zp10)` at `$C52B` | | `$C667–$C686` | 32 B | `CharacterIntroHeaderTiles` | 32 tile indices written straight to `$2007` — the header strip of the character-introduction screen. | `lda $C667,y` at `$C64B` (32 iterations, `cpy #$20` at `$C652`) | | `$C687–$C69A` | 20 B | `CharacterIntroDisplayPointerTable` | Ten little-endian 16-bit pointers to the per-character display records: `$C69B`, `$C6B3`, `$C6BE`, `$C6C9`, `$C6D4`, `$C6DE`, `$C6EA`, `$C6F3`, `$C6FD`, `$C707`. The index is `Zp87`, which the substate handler increments by two, so the entries are read at `$C687+Zp87` and `$C688+Zp87`. | `lda $C687,y` at `$C5A8` and `lda $C688,y` at `$C5AD` | | `$C69B–$C72D` | 147 B | `CharacterIntroDisplayData` | The ten per-character display records. Each is a PPU-update record — `[addr high][addr low][text…][$FF]` — optionally followed by a `$00` that marks the end of the record set and asks for the palette to be loaded. The text reads `CHARACTER ; NICKNAME`, `SHADOW`, `BLINKY`, `SPEEDY`, `PINKY`, `:POKEY`, `CLYDE`, plus the two `10 PTS` / `50 PTS` score legends. | copied by the loop at `$C5B4`–`$C5BC` (`lda (Zp00),y` / `cmp #$FF`), which is entered with the pointer from `CharacterIntroDisplayPointerTable` | | `$C72E–$C76D` | 64 B | `CharacterIntroPaletteTable` | The per-character sprite palettes. Each entry is four bytes (two sprite colours and two background colours); the copy at `$C5CD` takes 16 bytes from `$C72E+(index−2)*4` into `$0760+(index−2)*4`, so the 16 bytes are shared between neighbouring characters and only the first four differ. | `lda $C72E,y` at `$C5CD`, stored to `$0760,y` at `$C5D0` | | `$C76E–$C791` | 36 B | `CharacterIntroNameTiles` | 36 tile indices written straight to `$2007` — the row of character names. | `lda $C76E,y` at `$C614` (36 iterations, `cpy #$24` at `$C61B`) | | `$C792–$C8DB` | 330 B | - | **Code the tracer could not reach**: the rest of the character-introduction handlers, entered only through the indirect jumps of the substate and display tables. | reached only through `jmp (Zp10)` | | `$C9D8–$C9ED` | 22 B | `PlayerScorePpuRecords` | Two PPU-update records for the player-score boxes: eight tile-`$2D` tiles at `$22AD` and eight at `$22ED`. | copied into the record buffer at `$024B` by the score-drawing code | | `$C9EE–$C9F1` | 4 B | `PlayerScoreDigitTiles` | The four score digit tiles `$2D`, `$2F`, `$32`, `$34`. | `lda $C9EE,y` at `$C918` | | `$C9F2–$C9F5` | 4 B | `PlayerScoreMarkerPpuRecord` | A four-byte PPU-update record: address `$22C7`, tile `$20`, terminator `$FF`. | `lda $C9F2,y` at `$C9CB`, copied into `$024B` as the record prefix | | `$CA40–$CA53` | 20 B | - | 20 tile indices (values `$00`–`$1F`) used by the score/bonus display. The code indexes it with `(counter & 7) + base`, where `base` is `$00` or `$0A`, and separately with `base + 8`; the layout beyond "two eight-entry groups plus a two-entry group" is **not established** from this dump, so the block is left unnamed. | `lda $CA40,y` at `$CA0E` and `$CA29` | | `$CAD7–$CAE6` | 16 B | `GameModeStateHandlerTable` | Eight little-endian 16-bit addresses: the eight states of the game-mode state machine entered from `GameModeEntry` (`$C328`). | `lda $CAD7,y` at `$CACA` and `lda $CAD8,y` at `$CACF`; dispatched with `jmp (Zp10)` at `$CAD4` | | `$CAE7–$CB85` | 159 B | - | **Code the tracer could not reach**: the game-mode state handlers, entered only through the `jmp (Zp10)` of the table above. | reached only through `jmp (Zp10)` at `$CAD4` | | `$CC9C–$CCAD` | 18 B | `MazeWallTileTable` | The first of the two maze tile-index arrays, 16 bytes indexed by the actor's row segment (`inc Zp05` at `$CBB8`). The values `$B0`–`$C7` are the distinct wall-corner and wall-edge tiles of the maze. | `lda $CC9C,x` at `$CB9F`, written to the nametable buffer at `(Zp00),y` | | `$CCAE–$CCBF` | 18 B | `MazePathTileTable` | The second maze tile-index array, 16 bytes indexed the same way. The values are only `$00` (empty corridor) and `$02` (the pellet tile), so this array carries the maze's *paths* while `MazeWallTileTable` carries its walls. | `lda $CCAE,x` at `$CBA5` | | `$CCC0–$CCC5` | 6 B | `MazeColumnOffsetTable` | Three pairs of bytes: the column offset and the base row of each of the three vertical wall runs of the maze (`$44`/`$88`, `$44`/`$60`, `$50`/`$70`). | `lda $CCC0,x` at `$CB88` and `lda $CCC1,x` at `$CB8D` (X = `Zp06`, stepped by two) | | `$CCC6–$CCCB` | 6 B | `MazeRowRepeatTable` | Six repeat counts (`$06`, `$06`, `$03`, `$C0`, `$C1`, `$C2`) — how many rows each of the maze's horizontal wall runs occupies. | `lda $CCC6,x` at `$CB94` (X = `Zp07`) | | `$CCD1–$CCD8` | 8 B | `MazeTargetPositionTable` | Four little-endian 16-bit nametable positions: `$2317`, `$2319`, `$231B`, `$2357`. The code loads the high byte and adds a run-time offset to it. | `lda $CCD1,y` at `$CC32` and `lda $CCD2,y` at `$CC3A` (Y = (`Zp67`−1)×2) | | `$CCD9–$D289` | 1,457 B | - | **Code the tracer could not reach**: the maze-drawing and game-loop routines that follow the tables above, entered only through indirect jumps. | reached only through `jmp (Zp10)` | | `$D29C–$D3A5` | 266 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$D40E–$D415` | 8 B | `LevelHandlerPointerTable` | Four little-endian 16-bit addresses (`$D416`, `$D41A`, `$D435`, `$D431`), indexed by a level/state byte in `Zp51` times two. | `lda $D40E,y` at `$D401` and `lda $D40F,y` at `$D406`; dispatched with `jmp (Zp10)` at `$D40B` | | `$D416–$D505` | 240 B | - | **Code the tracer could not reach**: the four level handlers the table above points at. | reached only through `jmp (Zp10)` at `$D40B` | | `$D51F–$D556` | 56 B | - | 28 two-byte records. The first byte of each record is loaded into `ZpE3` and the low two bits of the second give a direction code (`and #$03` at `$D51A`). The record layout is **not established** from this dump, so the block is left unnamed. | `lda $D51F,y` at `$D512` and `lda $D520,y` at `$D517` | | `$D557–$D595` | 63 B | - | **Code the tracer could not reach**: the routine that uses the record table above. | reached only indirectly | | `$D5A1–$D763` | 451 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$D871–$D880` | 16 B | `DirectionPairTable` | Eight two-byte records, each a pair of distinct values drawn from `{0,1,2,3}`: `(3,2)`, `(2,3)`, `(3,0)`, `(0,3)`, `(1,2)`, `(2,1)`, `(1,0)`, `(0,1)`. These are the eight direction-pair codes used by the ghosts' movement logic. | `lda $D871,y` at `$D7E5` | | `$D881–$D884` | 4 B | - | Two little-endian 16-bit values (`$0000` and `$2C08`) used as a pointer pair. The table is only two entries long and its role is **not established**, so it is left unnamed. | `lda $D881,y` at `$D77E` and `lda $D882,y` at `$D783` | | `$D885–$D913` | 143 B | - | **Code the tracer could not reach**: the routine that uses the pointer pair above. | reached only indirectly | | `$D953–$D95A` | 8 B | `ActorHandlerTable` | Four little-endian 16-bit addresses: `$D95D` three times and `$D5A1` once — the per-actor-type movement handlers. | `lda $D953,y` at `$D946` and `lda $D954,y` at `$D94B`; dispatched with `jmp (Zp10)` at `$D950` | | `$D95B–$D98D` | 51 B | - | **Code the tracer could not reach**: the actor movement handlers. | reached only through `jmp (Zp10)` at `$D950` | | `$D9C6–$D9CB` | 6 B | - | Six bytes `$00`, `$00`, `$00`, `$01`, `$01`, `$00` added to a base value to give a position. The table is read as a byte array indexed by a direction code; its role is **not established**, so it is left unnamed. | `lda $D9C6,y` at `$D9BD` | | `$D9FA–$DA01` | 8 B | `ActorStateHandlerTable` | Four little-endian 16-bit addresses: `$DA21` three times and `$DA2D` once — the per-actor-state handlers. | `lda $D9FA,y` at `$D9ED` and `lda $D9FB,y` at `$D9F2`; dispatched with `jmp (Zp10)` at `$D9F7` | | `$DA02–$DA3F` | 62 B | - | **Code the tracer could not reach**: the actor-state handlers. | reached only through `jmp (Zp10)` at `$D9F7` | | `$DBEE–$DCFD` | 16 B | `PacManSpriteTileTable` | The tile byte of each OAM sprite record, 16 entries per actor type, indexed by `actor × 16 + frame`. The values `$4C`,`$03`,`$08`,`$07` … are Pac-Man's animation tiles. | `lda $DBEE,y` at `$DB5C`, stored into byte 1 (the tile byte) of the OAM record at `(Zp02)` | | `$DCFE–$DD0D` | 16 B | `GhostSpriteTileTable` | The tile byte of each OAM sprite record for the ghosts: `$4D`,`$4E`,`$53`,`$54`,`$4E`,`$52`,`$55`,`$4C` … | `lda $DCEE,y` at `$DB7F`, stored into the tile byte of the OAM record | | `$DD22–$DD31` | 16 B | `SpriteAttributeTable` | The attribute byte of each OAM sprite record: the values `$00`,`$40`,`$80`,`$C0` are the NES attribute bits (palette, horizontal flip, vertical flip). The same 16 bytes are also read as a *tile* source at `$DB39`, which is a genuine ROM quirk. | `ora $DD22,y` at `$DB6A` (combined with the base attribute `$0292,y` and stored into byte 2 of the OAM record); also `lda $DD22,y` at `$DB39` | | `$DE22–$DE31` | 16 B | `GhostSpriteAttributeTable` | The attribute byte of the ghosts' OAM records, same `$00`/`$40`/`$80`/`$C0` encoding. | `ora $DE22,y` at `$DB8D` | | `$DE32–$DE55` | 36 B | - | The remaining sprite tables and the code of the sprite-building routine. | not referenced by any instruction | | `$DF6C–$DF7B` | 16 B | `ActorVramAddressTable` | Eight little-endian 16-bit PPU addresses — one per actor on screen: `$20B4`, `$20A2`, `$22D4`, `$22C2`, `$28B4`, `$28A2`, `$2AD4`, `$2AC2`. These are the nametable positions the actors' sprites are drawn at. | `lda $DF6C,y` at `$DF2A` and `lda $DF6D,y` at `$DF30`, both written to `$2006` | | `$DF7C–$E0A9` | 302 B | - | **Code the tracer could not reach**: the routine that draws the actors' sprites. | reached only indirectly | | `$E0E6–$E1CD` | 232 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$E2E2–$E384` | 163 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$E49F–$E4B0` | 18 B | - | Nine 16-bit values whose second byte takes only the values `$61`, `$70` and `$80`; they are loaded as a pair into `$02`/`$03` and passed to the sprite routine at `$E474`. The meaning of the values is **not established**, so the block is left unnamed. | `lda $E49F,x` at `$E447` and `lda $E4A0,x` at `$E44C` | | `$E53C–$E549` | 14 B | `PlayerIntroPpuStream` | Two PPU-update records in the character-intro format: eight tiles `$B4`–`$BB` at `$2076`, three tiles `$B0`,`$B3`,`$B2` at `$20F7` and three tiles `$B1`,`$B3`,`$B2` at `$2177` — the player-name strip of the player-one / player-two screen. | `lda $E53C,y` at `$E506`, `$E513`, `$E51A` and `$E520` | | `$E54A–$EA18` | 1,231 B | - | **Code the tracer could not reach**: the rest of the player-introduction routine. | reached only indirectly | | `$EA94–$EB9E` | 267 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$EBB6–$EE8B` | 726 B | - | 726 bytes whose first bytes are all in the range `$00`–`$07` — the shape of the per-level ghost-behaviour table — but **no instruction in this dump reads it**, and its internal layout is not established. Recorded as unreferenced data. | not referenced by any instruction | | `$F01E–$F03D` | 32 B | `SoundHandlerTable` | Sixteen little-endian 16-bit addresses: `$F03E` (the "no sound" routine) followed by `$F04B`, `$F063`, `$F07B`, `$F093`, `$F0A3`, `$F0B3`, and then `$F03E` eight more times. Indexed by `(A & $0F) × 2`, i.e. by the sound request number. | `lda $F01E,x` at `$F011` and `lda $F01F,x` at `$F016`; dispatched with `jmp (ZpFE)` at `$F01B` | | `$F03E–$F048` | 11 B | - | **Code the tracer could not reach**: the "no sound" routine that nine of the table entries point at. | reached only through `jmp (ZpFE)` at `$F01B` | | `$F063–$F090` | 46 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$F0A3–$F0B0` | 14 B | - | **Code the tracer could not reach**: decodes cleanly as instructions and is entered through an indirect jump. | reached only indirectly | | `$F0E8–$F101` | 26 B | `NotePeriodTable` | Thirteen little-endian 16-bit note periods: `$03F9`, `$03C0`, `$038A`, `$0357`, `$0327`, `$02FA`, `$02CF`, `$02A7`, `$0281`, `$025D`, `$023B`, `$021B`, `$02F1`. The differences between consecutive entries shrink monotonically, which is the signature of a scale; the last entry (`$02F1`) is the base period the sound engine starts from. | `lda $F0E8,x` and `lda $F0E9,x` at `$EFB4`/`$EFB9`; the base period is read as `lda $F100` at `$EE9C` and `lda $F101` at `$EEA1` | | `$F102–$FFF9` | 3,848 B | - | `$FF` fill to the end of the PRG image. | not referenced by any instruction | | `$FFFA–$FFFF` | 6 B | `CpuVectors` | The three interrupt vectors: NMI = `$C10E` (`NmiHandler`), RESET = `$C033` (`Reset`), IRQ/BRK = `$C17E` (`IrqHandler`, a bare `rti`). | the CPU fetches them from `$FFFA`–`$FFFF` when an interrupt is taken | ### 11.1 Data structures in RAM (not ROM tables) - The PPU-update record buffer at `$024B` and the score/actor state at `$028C`–`$0293` - The palette buffer at `$0299` (32 bytes), filled from `TitleScreenInitialPalette` - The character palettes at `$0760`–`$076F`, filled from `CharacterIntroPaletteTable` - The warm-start signature at `$0052`–`$0060`, compared against `WarmStartSignature` - The sound-engine base period at `$F4`/`$F5`, loaded from the last entry of `NotePeriodTable` --- ## 12. Summary of Execution Flow ``` Power On / Reset │ ▼ Reset ($C033) ───────────────────────────────────────────────┐ │ sei, cld, disable PPU, wait 2× VBlank │ │ clear RAM $0000–$003D, clear pages $0100–$07FF │ │ PPUMASK=$06, scroll=$00 │ │ check warm-start signature "HIROKI AOYAGI" + $AA,$55 │ │ valid → skip full clear; invalid → clear + save sig │ │ set flags, optional 16-byte swap ($67↔$77) │ │ APUCTRL=$1F, APUFRAMECNT=$C0 │ │ jsr InitAudio, jsr ClearAudioWorkspace │ │ PPUCTRL=$88 (NMI enabled), $48=$FF (suspended) │ │ jmp MainEntry │ │ │ MainEntry ($C573) │ │ $40=$1, wait for NMI to clear it │ │ PPUCTRL=$88, PPUMASK=$00, clear OAM │ │ clear cocktail/player flags, $41/$42/$4C/$87/$88 │ │ init nametables, attributes, palette buffers │ │ select PPUCTRL ($8A), set ppuCtrlShadow │ │ │ MainDispatchLoop ($C645) ─────────────────────────────────────┤ │ $40=$1, wait for NMI │ │ Y = gameState ($3F) │ │ load handler from MainStateHandlerTable ($C267) │ │ jmp ($0010) → state handler │ │ │ ├─ State $00: InitialStateHandler │ │ ├─ controller bits $0C → gameState=$06, $0F=$5A │ │ │ → jump MainEntry (restart sync) │ │ └─ else inc $42; if $42==$F0 → reset, gameState=$06 │ │ │ ├─ State $02: PlayerSelectionState │ │ ├─ 1P/2P selection via controller bits $04/$08 │ │ ├─ timeout ($87/$88 reaches $0200) → gameState=$04 │ │ └─ confirm (bit $08) → clear suspended, jsr ClearAudioWorkspace, │ jmp GameModeEntry │ │ │ ├─ State $04: State04Handler │ │ ├─ controller bits $0C → gameState=$02, restart │ │ └─ Y=$87 → dispatch via State04SubstateTable ($C52E) │ │ ├─ $00: State04Initialize (PPU setup, $87+=2) │ │ ├─ $02–$12: State04TimedSubstate ($30 frames) │ │ ├─ $14: State04LongDelay ($80 frames) │ │ └─ $16: State04FinalPhase → $47=$1, │ │ jmp GameModeEntry │ │ │ └─ State $06: TimedStateTransition │ │ dec $0F; if nonzero → MainDispatchLoop │ │ else gameState=$02 → MainDispatchLoop │ │ │ └─ loop back to MainDispatchLoop │ │ │ GameModeEntry ($C328) [from SetSelectionFlagAndContinue] │ │ │ ├─ PPUCTRL=$88, wait VBlank, PPUMASK=$00 │ ├─ clear gameplay state, HUD labels, score fields │ ├─ if not suspended: clear player state, roundNumber=$3 │ │ else: roundNumber=$1 │ ├─ common init: roundDisplayValue=$FF, PPUCTRL=$88 │ │ │ GameModeFrameSync ($C360) ────────────────────────────────────┤ │ $40=$1, wait for NMI │ │ │ ├─ if suspended && controller bits $0C → gameState=$02, │ │ jmp MainEntry (restart) │ │ │ ├─ if stateTimer != 0 → dec, return to @WaitForNextFrame │ │ │ └─ Y = gameState → dispatch via GameModeStateTable ($CAD7)│ │ │ ├─ $00: GameModeState00 │ ├─ $02: GameModeState02 │ ├─ $04: GameModeState04 (pause/input handler) │ │ ├─ $060F != 0 → return to frame sync │ │ ├─ edge-detect controller bit $08 ($49) │ │ │ → new press: inc $4A, install pause cmds │ │ │ → bit 0 of $4A toggles pause display │ │ └─ not paused, bit0 of $4A clear → gameplay │ │ helper chain → return to frame sync │ ├─ $06–$0C: GameModeState06/08/0A/0C │ └─ $0E: InitializeGameplayTransitionState │ │ │ └─ loop back to GameModeFrameSync │ │ │ └─ (eventually enters gameplay via $0E handler) │ │ NMI ($C10E) [once per frame] │ │ │ ├─ save A, X, Y │ ├─ PPUMASK=$1E │ ├─ OAM DMA ($4500–$45FF → $0200–$02FF) │ ├─ jsr FlushGameplayPpuUpdates ($DE3B) │ ├─ jsr ReadNearbyNametableTiles │ ├─ jsr UpdatePalettes ($3F00/$3F10 from $0299/$02A9) │ ├─ restore scroll ($41,$42 → PPUSCROLL) │ ├─ construct PPUCTRL from $47,$46,$43 │ ├─ strobe JOY1, read both controllers (8 bits each) │ │ → $4D (current), $4E (newly pressed) │ ├─ select active controller → $4F (previousInput) │ ├─ inc frameCounter ($4B) │ ├─ if $48==0: jsr RunLevelScript │ ├─ restore registers, rti │ │ │ IrqHandler ($C17E): rti (unused) │ │ Audio Engine (from NMI via RunLevelScript/ClearAudioWorkspace)│ ├─ 16 eight-byte channel slots scanned │ │ │ values $01–$04 activate a channel │ │ │ 4-byte parameters copied via $F6 pointer │ ├─ UpdateAudioChannels ($EF33): │ │ │ decrement countdowns, fetch command bytes, │ │ │ decode ($F0+=special, $C0+=delay, else=note), │ │ │ write APU registers $4000–$400F │ └─ UpdateAudio ($F8DE): 4 channels + SFX + DMA trigger │ ``` --- ## 13. Notes - **Build verification:** `make` in `copilot/` produces `reconstructed.nes`, compared whole-ROM against `rom.nes`. - **Pass 3 data tables:** every data block in the PRG is interpreted and listed in the **Pass 3 data-table inventory** (section 11), with the address range, the name, the structure and the consuming instruction for each. The PPU-update record formats, the maze tile tables, the sprite tile/attribute tables and the note-period table are all decoded from the consuming code; the blocks that no instruction in this dump reads are recorded as unreferenced data, and the blocks that decode cleanly as instructions are recorded as code the tracer could not reach (the ROM reaches them through `jmp (Zp10)` indirect jumps, which a static tracer cannot follow). - Vectors confirm NMI at $C10E, RESET at $C033, IRQ at $C17E (a bare `rti`, unused). - The foreground main loop (`MainDispatchLoop` at $C645) is strictly NMI-synchronized via `$40`: NMI clears it each frame, and the main code spins waiting for it to clear before dispatching to the next state handler. - `GameModeEntry` ($C328) is a second, separate state machine entered from the state-$04 final phase. It has its own frame sync (`$40`) and its own 8-state dispatch table (`GameModeStateHandlerTable` at $CAD7), leading into gameplay initialization. - State $04 contains a nested secondary state machine (`State04SubstateHandlerTable` at $C52E) with 12 substate offsets, implementing a timed character-introduction sequence (GHOST names, nicknames, scoring legend). - The warm-start signature `"HIROKI AOYAGI"` at $0052 allows the game to preserve RAM across a soft reset; the pattern itself lives in the ROM at `WarmStartSignature` ($C0FF). - Many routine labels are address-based (`State04TimedSubstate`, `LoadState04DisplayData`, etc.) reflecting the automated first-pass disassembly; semantic naming is provisional. - `symbols.inc` (embedded at the top of `prg.s`) provides NES hardware register names, controller masks, and extensive zero-page symbol definitions. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. --- ## 14. Naming residue — address-derived entry points Every `jsr`/`jmp` target in this project now carries a semantic name except **4**, which keep the honest address-derived `SubCCXXXX` form because the dump does not reveal what they are for: | Label | Address | Why the dump does not settle the name | |--------|---------|-------------------------------------| | `SubCC5D8` | `$C5D8` | Points PPUADDR at VRAM `$20C0` and sets a count of `$17` (23) tiles, then writes that many tiles from the following table. The screen that owns VRAM `$20C0` is not identified by anything in this dump, so naming it "the screen that writes 23 tiles at $20C0" would only restate the code. | | `SubCDA5A` | `$DA5A` | 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. | | `SubCE419` | `$E419` | Points the score writer at VRAM `$2083` with a stride of `$09` and at the player-2 score variable. The dump does not identify the screen row that block occupies. | | `SubCEA19` | `$EA19` | Adds the 16-bit word at `$001A` (Pac-Man's motion record) to the word at `$0274` and then tests the high byte against `$C0`. The two words are positions, but the dump does not say whose positions they are. | The other 41 entry points are named, and the names follow the analysis already in this document: `UpdatePalettes` ($C17F), `FlushGameplayPpuUpdates` ($DE7E), `ReadNearbyNametableTiles` ($E2A2), `LoadState04DisplayData` ($C5A6), `InitializeState04PpuData` ($C608), `BuildOamBuffer` ($E1DA), `InitAudio` ($EE8C), `RunLevelScript` ($EED0), `UpdateAudioChannels` ($EF33), `ApplyAudioCommand` ($EFA2) and `ReadScriptParam` ($F0C3) among them.