# Burger Time (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Burger Time (U) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `prg.s` in this project folder. The rebuild is verified **byte-identical** to the original `rom.nes`. --- ## 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 files | `prg.s`, `header.s`, `chr.s` (plus `symbols.inc` / `locals.inc`) | | Output | `reconstructed.nes` (24,592 bytes) | ### Interrupt vectors (CPU $FFFA–$FFFF) | Vector | Address | Handler | |--------|---------|---------| | NMI | $FF7F | `NmiHandler` | | RESET | $FD24 | `ResetHandler` | | IRQ / BRK | $FD24 | `ResetHandler` (reused) | --- ## 2. Build Status `make` in this folder assembles `header.s`, `prg.s`, and `chr.s`, links them via `ld65`, and verifies the result against `rom.nes`. **Verification result: BYTE-IDENTICAL** — headers, CHR, and all PRG bytes match exactly. **Pass 3 data-table work is complete.** All 71 named data tables in the PRG carry a ROM-specific name (the old `LookupTable_Cxxx` placeholders are gone) and every one of them — plus the 25 inline data blocks — is listed with its structure and its consumer in the *Pass 3 data-table inventory* section below. The original ROM is kept in the folder as `Burger Time (U) [!].nes`; `rom.nes` is the copy the Makefile compares against, so no symlink is needed. --- ## 3. Reset — CPU $FFFC → $FD24 **`ResetHandler`** ($FD24) On power-on or reset: 1. `sei` — disable maskable interrupts. 2. `cld` — clear decimal mode. 3. `lda #$00 / sta PPUCTRL / sta PPUMASK` — disable PPU rendering and NMI. 4. **Wait for two VBlanks** — `Flow_FD2E` and `Flow_FD33` each poll `PPUSTATUS` ($2002) for the VBlank bit. 5. `jsr SetDefaultScroll` ($FFEB) — set scroll to ($08, $00). 6. `FrameCounter` ($14) = $00; `FrameCounterReload` ($13) = $08. 7. `JOY2` ($4017) = $80 — select controller latch mode / disable IRQ. 8. Stack pointer = $FF. 9. `jsr InitializeRuntimeState` ($FD63) — clear NmiCompletedFlag ($0E), GameMode ($4C), PendingFrameTask ($8F), PlayerDeathFlag ($4F), StageCompleteFlag ($4E); set PPUCTRL shadow to $90; clear nametables; load initial palette; wait for NMI; set PPUMASK shadow to $1A. 10. `jsr InitializeGameSystems` ($C00E) — process game state at $C76E and $CB53; set up initial OAM positions ($0700=$70, $0701=$10); clear $32; call `SetMainState` to enter the first state. 11. Falls into **`Flow_FD4F`** ($FD4F) — the main loop. --- ## 4. Main Loop — State Machine at $FD4F **`Flow_FD4F`** ($FD4F) is the central main loop. Every iteration: 1. `jsr WaitForNmi` ($FD9D) — block until NmiCompletedFlag ($0E) is set, then clear it. 2. Load `MainGameState` ($16), multiply by 2, use as index into a pointer table: - Low byte from `MainStateHandlerPointerLo` ($C000) - High byte from `MainStateHandlerPointerHi` ($C001) - The two tables are interleaved in the same $C000–$C00D region. 3. Indirect jump (`jmp (DataPointerLo)`) to the state handler. ### State Dispatch Table (CPU $C000–$C00D) The table is structured as alternating low/high bytes indexed by `MainGameState * 2`: | State | CPU Addr | Handler Routine | Purpose | |-------|----------|-----------------|---------| | 0 | $C3F8 | `RunFrameSynchronizedTask_C3F8` | Initial / boot frame task | | 1 | $C04E | `HandleTitleState` | Title screen | | 2 | $C12F | `HandleGameStartState` | Game start / level init | | 3 | $4DC2 | *(unmapped / bank-switched)* | *(state 3 entry)* | | 4 | $C24D | `Flow_C24D` | Stage transition / between-levels | | 5 | $C14F | `HandleGameplayState` | Main gameplay | | 6 | $C1AA | `HandlePlayerDeathState` | Player death sequence | | *(state 7+)* | *(from table)* | *(various)* | *(additional states)* | `SetMainState` ($FF7C) stores the accumulator into `MainGameState` ($16) and returns — used by every state handler to transition to the next state. --- ## 5. NMI / Frame Heartbeat — `NmiHandler` ($FF7F) The NMI fires once per frame and is the real-time driver: 1. **Save registers** (A, X, Y on stack). 2. **OAM DMA** — write $00 to OAMADDR ($2003), then $07 to OAMDMA ($4014) to DMA sprite data from $0700–$07FF. 3. Set `NmiCompletedFlag` ($0E) = $01 — signals the main loop that a frame has elapsed. 4. Read `PPUSTATUS` ($2002) to reset the address latch. 5. **Stage complete handling** — if `StageCompleteFlag` ($4E) is set, `jsr ProcessGameState_CEA3` ($CEA3), then clear the flag. 6. `jsr ProcessPendingFrameTask` ($C029) — if a frame task is pending, advance its counter. 7. If `GameMode` ($4C) is nonzero, `jsr ProcessGameState_F493` ($F493) — per-frame gameplay/audio update. 8. **Restore registers, `rti`.** --- ## 6. State Handlers ### 6.1 Title State — `HandleTitleState` ($C04E) - Sets `GameMode` ($4C) = $00 and `Zp_57` ($57) = $00. - `jsr ProcessGameState_C7F7` ($C7F7) — title-specific game state processing. - `StateInitializedFlag` ($31) = $01. - `jsr ProcessGameState_CB53` ($CB53) and `jsr ProcessGameState_C81B` ($C81B). - `jsr EnableBackgroundAndSprites` ($C3EE) — enables rendering. - `jmp Flow_C233` ($C233) — clears stage state, sets `MainGameState` = 4, returns to main loop. ### 6.2 Game Start State — `HandleGameStartState` ($C12F) - `jsr ProcessGameState_C7F7` ($C7F7). - `GameActiveFlag` ($4D) = $01; `StateInitializedFlag` ($31) = $01. - `jsr ClearNametable2000` ($FDD0) — clear nametable at $2000. - `jsr ProcessGameState_C81B` ($C81B) and `jsr ProcessGameState_C977` ($C977). - Decrement `Ram_0400` ($0400). - `jsr ClearNametable2000` again. - Set `MainGameState` = 6 → gameplay state. - Return to main loop via `Flow_FD4F`. ### 6.3 Gameplay State — `HandleGameplayState` ($C14F) The core gameplay state. Checks `Zp_52` ($52): - If `Zp_52` is nonzero → `Flow_C161` (second-player / alternate player path). - If zero → `Flow_C153` (first player path). Checks `PlayerDeathFlag` ($4F): - Bit 0 = player 1 dead; bit 1 = player 2 dead. - If a player is dead → `Flow_C19E` → sets `MainGameState` = 5 (player death state). - Otherwise continues gameplay. When transitioning to death (`Flow_C177`): - Disable background/sprites, set state initialized, process game state, re-enable rendering. - Store pending sound command $03 (death sound), set GameMode = $01. - Run a frame-synchronized task at $CA80, store sound command $04. - Jump to `Flow_C233` → `MainGameState` = 4 (stage transition). ### 6.4 Player Death State — `HandlePlayerDeathState` ($C1AA) - Disable rendering, clear nametable. - Loads player-specific data based on `CurrentPlayerIndex` ($50): X = $C1 (player 1) or $12 (player 2). - `jsr ProcessGameState_C715` ($C715) with player data. - Store player sprite Y at $0701. - Load palette, wait for NMI, set PPUCTRL/PPUMASK. - Run frame-synchronized task, clear nametable, process game state at $CEC8. - Set `MainGameState` = 5, return to main loop. ### 6.5 Stage Complete / Between-Stage State — `HandleStageCompleteState` ($C22A) / `Flow_C233` ($C233) - Disable rendering, process game state, enable rendering. - `Flow_C233`: clear `Ram_046E`–$470, `Zp_2E`, `Zp_2F`, `Ram_045A`. - Set `MainGameState` = 4 → stage transition state. - Return to main loop. `Flow_C24D` ($C24D) — the stage transition update (runs every 8th frame via `Zp_15` counter): - Processes a long chain of game state routines: $C9F9, $D7D3, $D980, $E11E, $E0B7, $E71A, $E6AE. - If `GameMode` != 0: reads controllers for current player. - More game state processing: $D1C1, $D6BF, $CB6F, $DB45, $DBD7, $DC82, $DD61, $EA1A, $E7DE, $E81A. - If `GameMode` != 0: `ProcessGameState_C5FF` ($C5FF) — score/remaining-life logic. - Handles player lives (`Zp_1B` for player 1, `Zp_23` for player 2), wraps at 7. - Clears nametable, sets `MainGameState` = 5 or 6 depending on mode. --- ## 7. Core Gameplay Update Loop Every 8 frames (when `Zp_15 & $07 == 0`), `Flow_C258` ($C258) runs the primary gameplay update: | Routine | Address | Purpose | |---------|---------|---------| | `ProcessGameState_C9F9` | $C9F9 | Object/enemy movement | | `CalculateAndClampValue_D7D3` | $D7D3 | Value clamping | | `ProcessGameState_D980` | $D980 | Object processing | | `ProcessGameState_E11E` | $E11E | Collision / interaction | | `ProcessGameState_E0B7` | $E0B7 | State update | | `ProcessGameState_E71A` | $E71A | Animation | | `ProcessGameState_E6AE` | $E6AE | Object cleanup | | *(player input)* | — | Controller reading when GameMode != 0 | | `ProcessGameState_D1C1` | $D1C1 | Player movement | | `ProcessGameState_D6BF` | $D6BF | Player animation | | `ProcessGameState_CB6F` | $CB6F | Burger/part assembly | | `ProcessGameState_DB45` | $DB45 | Enemy AI | | `ProcessGameState_DBD7` | $DBD7 | Collision detection | | `ProcessGameState_DC82` | $DC82 | Score / points | | `ProcessGameState_DD61` | $DD61 | Bonus items | | `ProcessGameState_EA1A` | $EA1A | Pepper / power-up | | `ProcessGameState_E7DE` | $E7DE | Round timing | | `ProcessGameState_E81A` | $E81A | Level progression | | `ProcessGameState_C5FF` | $C5FF | Lives / continues (GameMode != 0) | --- ## 8. Audio Engine **`UpdateAudioEngine`** ($F478) Called from: - `Flow_C368` ($C368) — during title/transition states - `RunFrameSynchronizedTask_C3F8` ($C3F8) — during frame-synchronized tasks - `Flow_C615` ($C615) — during gameplay state - `Flow_C7D6` ($C7D6) — tail call from another path The audio engine: 1. Clears audio RAM at $052F (167 bytes, X from $A7 down). 2. Clears APU channel volume registers $4000–$400F (20 bytes). 3. Writes $00 to SND_CHN ($4015) — disable all channels initially. 4. Sets `Ram_05A3` ($05A3) = $38. 5. Processes sound commands and music updates via subroutines at $F4B3, $F5B3, $F748, $F6E4. **`StorePendingSoundCommand`** ($C026) — stores a sound command in `PendingSoundCommand` ($90), processed by the NMI or audio engine. **`ProcessPendingFrameTask`** ($C029) — processes `PendingFrameTask` ($8F); if $FF, resets to $00 and advances; otherwise increments the task counter up to $F1. --- ## 9. PPU / Rendering ### `EnableBackgroundAndSprites` ($C3EE) Waits for PPU VBlank, loads `PpuMaskShadow` ($11), ORs with $18 (enable background + sprites), calls `SetPpuMaskShadow`. ### `DisableBackgroundAndSprites` ($C3E4) Waits for PPU VBlank, loads `PpuMaskShadow`, ANDs with $E7 (disable background + sprites). ### `SetPpuControlShadow` ($C3D8) / `SetPpuMaskShadow` ($C3DE) Store accumulator to the shadow register ($0C/$0D) and to the corresponding PPU register ($2000/$2001). ### `ClearNametables` ($FDAE) Clears both nametables ($2000 and $2800) via `ClearNametable2000` and `ClearNametable2800`. ### `LoadPalette` ($FDF0) Waits for PPU VBlank, sets PPU address to $3F00 (palette), writes 16 bytes of palette data from a pointer in X/Y. ### `SetDefaultScroll` ($FFEB) Writes $08 then $00 to PPUSCROLL ($2005). ### `DecodeNametableStream` ($FE74) Decodes a run-length-encoded nametable stream from a pointer (X/Y) into PPU memory. Used extensively during screen initialization (`RunFrameSynchronizedTask_C3F8` calls it multiple times for different screen regions). --- ## 10. Controller Input **`ReadControllers`** ($FE2B) Reads both controller ports ($4016/$4017) by strobing JOY1, then shifting in 8 bits per controller. Results are packed into scratch variables: - `ScratchValue0` ($01), `ScratchValue1` ($02), `ScratchIndex` ($03) — raw button bits. - The result is processed into directional/button flags stored in `Zp_2F` ($2F), checked with `and #$0C` (start/select) and `and #$08` (specific button) throughout the code. --- ## 11. Key Zero-Page / RAM Symbols | Address | Symbol | Use | |---------|--------|-----| | $0E | NmiCompletedFlag | NMI frame-sync flag | | $10 | PpuControlShadow | $2000 mirror | | $11 | PpuMaskShadow | $2001 mirror | | $13 | FrameCounterReload | Frame counter reload value | | $14 | FrameCounter | Frame tick counter | | $15 | Zp_15 | Frame divider (gameplay runs every 8 frames) | | $16 | MainGameState | Current state machine state | | $1B | Zp_1B | Player 1 lives counter | | $1F | Zp_1F | (unnamed) | | $23 | Zp_23 | Player 2 lives counter | | $2E–$2F | Zp_2E / Zp_2F | Controller/button state | | $31 | StateInitializedFlag | State init flag | | $32–$35 | Zp_32–$35 | Indirect pointers / coordinates | | $3F | Zp_3F | (unnamed) | | $44 | Zp_44 | (unnamed) | | $4C | GameMode | Game mode flag (0=title/demo, nonzero=gameplay) | | $4D | GameActiveFlag | Game active flag | | $4E | StageCompleteFlag | Stage completed flag | | $4F | PlayerDeathFlag | Player death flags (bit0=P1, bit1=P2) | | $50 | CurrentPlayerIndex | Current player (0 or 1) | | $52 | Zp_52 | Gameplay sub-state | | $56 | Zp_56 | (unnamed) | | $57 | Zp_57 | Title state variable | | $5A–$5B | Zp_5A / Zp_5B | (unnamed) | | $8F | PendingFrameTask | Pending frame task counter | | $90 | PendingSoundCommand | Pending sound command | | $95–$96 | DispatchPointerLo / DispatchPointerHi | State dispatch indirect pointer | | $0400 | Ram_0400 | (unnamed) | | $040D–$040F | Ram_040D–$040F | (unnamed) | | $045A–$045F | Ram_045A–$045F | (unnamed) | | $0460–$0467 | Ram_0460–$0467 | Object/enemy state | | $046E–$0470 | Ram_046E–$0470 | Stage state | | $04E0 | Ram_04E0 | (unnamed) | | $0500–$0535 | Ram_0500–$0535 | Object arrays | | $052F–$05A7 | Ram_052F–$05A7 | Audio engine state | | $0536 | CurrentObjectIndex | Current object being processed | | $0700–$07FF | Ram_0700–$07FF | OAM buffer + sprite data | --- ## 12. Summary of Execution Flow ``` Power On / Reset │ ▼ ResetHandler ($FD24) ───────────────────────────────┐ │ sei, cld, disable PPU, wait 2× VBlank │ │ SetDefaultScroll, init frame counters │ │ InitializeRuntimeState ($FD63) │ │ ├─ clear NmiCompletedFlag, GameMode, etc. │ │ ├─ set PPUCTRL shadow=$90, clear nametables │ │ ├─ load palette, wait NMI │ │ └─ set PPUMASK shadow=$1A │ │ InitializeGameSystems ($C00E) │ │ ├─ process game state ($C76E, $CB53) │ │ ├─ set initial OAM ($0700=$70, $0701=$10) │ │ └─ SetMainState → first state │ ▼ │ Flow_FD4F ($FD4F) ───────────────────────────────────┤ │ │ ├─ WaitForNmi ($FD9D) ◄────── waits for NMI ───┘ │ │ ├─ MainGameState ($16) → dispatch table │ │ ├─ State 0: $C3F8 (frame sync task) │ │ ├─ State 1: $C04E (HandleTitleState) │ │ ├─ State 2: $C12F (HandleGameStartState) │ │ ├─ State 4: $C24D (stage transition) │ │ ├─ State 5: $C1AA (HandlePlayerDeathState) │ │ ├─ State 6: $C14F (HandleGameplayState) │ │ └─ ... │ │ │ └─ (state handler returns via SetMainState) │ │ │ ▼ │ back to Flow_FD4F ◄───────────────────────────────┘ NMI ($FF7F) [every frame] ├─ OAM DMA from $0700 ($4014) ├─ NmiCompletedFlag ($0E) = $01 ├─ StageCompleteFlag ($4E) handling ($CEA3) ├─ ProcessPendingFrameTask ($C029) ├─ if GameMode != 0: ProcessGameState_F493 ($F493) └─ rti Gameplay Update (every 8 frames, Flow_C258 at $C258) ├─ object movement & collision ($C9F9–$E6AE) ├─ controller input (when GameMode != 0) ├─ player movement & animation ($D1C1, $D6BF) ├─ burger assembly, enemy AI ($CB6F, $DB45) ├─ collision, score, bonus ($DBD7–$DD61) ├─ pepper, round timing ($EA1A–$E81A) └─ lives/continues ($C5FF) ``` --- ## Pass 3 data-table inventory Every data block in the PRG, what its bytes are, and the instruction in this dump that reads it. The PRG is a single 16 KB bank at `$C000-$FFFF`, so every address below is both the CPU address and the PRG offset plus `$C000`. Burger Time's tables are frequently split across two labels — the game stores a 16-bit value as two byte arrays (low half and high half) and loads each with its own `lda`. Where that is the case, the two halves are listed as two rows that together form one table. ### Named tables | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000` | 1 | `MainStateHandlerPointerLo` | Low byte of the 7-entry main-state handler pointer table (`$C000-$C00D`) | `lda MainStateHandlerPointerLo,x` at `$FD56` | | `$C001-$C00D` | 13 | `MainStateHandlerPointerHi` | High byte of the same table. Together the two halves give the 7 little-endian pointers `$C3F8, $C04E, $C12F, $C1AA, $C24D, $C14F, $C1AA` — the handlers for the states selected by `MainGameState` ($16) | `lda MainStateHandlerPointerHi,x` at `$FD5B`; the pair is executed with `jmp ($95)` at `$FD5D` | | `$C7F3-$C7F6` | 4 | `DecoSignatureString` | The ASCII string "DECO". The routine at `$C78C` compares four bytes of RAM against it and, when they differ, writes "DECO" back — a Data East signature stamp | `cmp DecoSignatureString,y` at `$C792`; `lda DecoSignatureString,y` at `$C7A1` | | `$C922-$C927` | 6 | `AttractModeSpriteTileTable` | 6 sprite tile indices (`$AC,$BC,$CC,$8C,$CC,$AC`) used by the attract-mode sequence; the value lands in `Zp_58` ($58), which nothing else in this dump reads | `lda AttractModeSpriteTileTable,x` at `$C91C` | | `$CA74` | 1 | `TitleTextDestinationPointerHi` | High byte of a 7-entry table of nametable addresses where the title/attract text is written (`$6720, $2067, $2071, $2066, $206F, $2079, $2020`) | `lda TitleTextDestinationPointerHi,y` at `$CA5E` | | `$CA75-$CA7F` | 11 | `TitleTextDestinationPointerLo` | Low byte of the same table | `lda TitleTextDestinationPointerLo,y` at `$CA63`; the pair becomes the pointer `Zp_34/Zp_35` handed to `DecodeAttributeStream` at `$CA70` | | `$CB6B-$CB6E` | 4 | `InitialObjectCountTable` | 4 initial counts (`$02,$03,$04,$05`) copied to `Zp_8B`, `Zp_8C`, `Zp_8D` (`$8B-$8D`) when the game systems start | `lda InitialObjectCountTable,x` at `$CB5B` | | `$CCE9-$CE24` | 316 | `ObjectEventHandlerPointerTable` | A table of interleaved 16-bit little-endian code addresses: a count byte followed by that many handler addresses, repeated. The first entries are `$CD95`, `$CDBD`, `$CD95`, `$CD9D`, `$CDB5`, `$CDAD`, … | `lda ObjectEventHandlerPointerTable,x` at `$CCC3` and `$CCC9`; the pair is executed with `jmp ($36)` at `$CCDD` | | `$D268` | 1 | `IngredientPlacementOffsetXTable` | Low (X) half of a 5-pair table of 16-bit signed placement offsets (`$00,$10`, `$10,$00`, `$F0,$00`, `$00,$F0`, `$00,$10`) | `lda IngredientPlacementOffsetXTable,x` at `$D1EC` | | `$D269-$D271` | 9 | `IngredientPlacementOffsetYTable` | High (Y) half of the same table; the two halves together are the five (X, Y) offsets at which a burger ingredient is placed | `lda IngredientPlacementOffsetYTable,x` at `$D1F6` | | `$D272-$D27D` | 12 | `DirectionStateIndexTable` | 12 small state indices (`$00,$06,$08,$00,$04,$00,$00,$00,$02,$00,$00,$00`) selected by the player's directional input (`Zp_2E & $0F`); the result is compared with `Ram_045A` and scaled by 16 into `Ram_044F` | `lda DirectionStateIndexTable,y` at `$D223` | | `$D3E5` | 1 | `EnemyScriptPointerLo` | Low byte of a table of 16-bit little-endian pointers to per-enemy byte scripts (the scripts are terminated by an `$FF` byte) | `lda EnemyScriptPointerLo,y` at `$D2CD` | | `$D3E6-$D444` | 95 | `EnemyScriptPointerHi` | High byte of the same table | `lda EnemyScriptPointerHi,y` at `$D2D2`; the pair is read as `lda ($34),y` at `$D2D9` | | `$D445` | 1 | `EnemySpriteAttributeTable` | Low byte of the per-enemy sprite attribute pair | `lda EnemySpriteAttributeTable,x` at `$D373` | | `$D446-$D44E` | 9 | `EnemySpriteTileTable` | High byte of the same pair; the two halves give the sprite attribute and tile of a spawned enemy | `lda EnemySpriteTileTable,x` at `$D38D` | | `$D475` | 1 | `LevelPatternPointerLo` | Low byte of a table of 16-bit little-endian pointers to the per-level pattern scripts; the entries point at code inside this same block (`$D47F`, `$D485`, `$D4C1`, `$D4FD`, `$D51F`, `$D5A9`, …) | `lda LevelPatternPointerLo,y` at `$D457` | | `$D476-$D6BE` | 585 | `LevelPatternPointerHi` | High byte of the same table. The block also holds the pattern code the pointers refer to, so the table and the routines it selects share one region | `lda LevelPatternPointerHi,y` at `$D45C` | | `$D762-$D766` | 5 | `BurgerItemTileTableA` | 5 sprite tile indices (`$24,$24,$24,$2C,$28`) for the first of the four burger-item events, one entry per ingredient slot | `lda BurgerItemTileTableA,y` at `$D6EB`; the index is `Ram_0459 >> 1` | | `$D767-$D76B` | 5 | `BurgerItemTileTableB` | The same for the second event (`$04,$04,$04,$20,$1C`) | `lda BurgerItemTileTableB,y` at `$D702` | | `$D76C-$D770` | 5 | `BurgerItemTileTableC` | The same for the third event (`$24,$24,$24,$2C,$28`) | `lda BurgerItemTileTableC,y` at `$D714` | | `$D771-$D775` | 5 | `BurgerItemTileTableD` | The same for the fourth event (`$04,$04,$04,$20,$1C`) | `lda BurgerItemTileTableD,y` at `$D726` | | `$D776-$D77A` | 5 | `BurgerItemAttributeTableA` | 5 sprite attribute bytes (`$EC,$EC,$EC,$EA,$EA`) written to the tile byte of OAM sprite 6 (`Ram_0719`) for the first event | `lda BurgerItemAttributeTableA,y` at `$D6F1` | | `$D77B-$D77F` | 5 | `BurgerItemAttributeTableB` | The same for the second event | `lda BurgerItemAttributeTableB,y` at `$D708` | | `$D780-$D784` | 5 | `BurgerItemAttributeTableC` | The same for the third event | `lda BurgerItemAttributeTableC,y` at `$D71A` | | `$D785-$D789` | 5 | `BurgerItemAttributeTableD` | The same for the fourth event | `lda BurgerItemAttributeTableD,y` at `$D72C` | | `$DB0F-$DB12` | 4 | `PlayerMoveSpeedTable` | 4 movement-speed values (`$0F,$03,$07,$0F`), one per player difficulty level, copied to `Ram_0430`-`$Ram_0435` | `lda PlayerMoveSpeedTable,x` at `$DAE2` | | `$DB13-$DB17` | 5 | `PlayerBonusSpeedTable` | 5 values (`$03,$07,$0F,$03,$07`) stored in `Ram_0437`; the index selects the one- or two-player variant | `lda PlayerBonusSpeedTable,x` at `$DB02` | | `$DB18-$DB1B` | 4 | `PlayerTimerTable` | 4 timer values (`$04,$03,$03,$03`) copied to `Ram_0440`-`$Ram_0445` | `lda PlayerTimerTable,x` at `$DAED` | | `$DB1C-$DB20` | 5 | `PlayerExtraLifeTable` | 5 values (`$02,$02,$02,$01,$01`) stored in `Ram_0447` | `lda PlayerExtraLifeTable,x` at `$DB08` | | `$DB21-$DB2C` | 12 | `IngredientTimingTableA` | 12 timing values (`$03,$03,$02,$04,$00,$02,$05,$05,$02,$04,$00,$02`) stored in `Ram_045B`; the index is `6 + Ram_0403`, i.e. the ingredient slot being assembled | `lda IngredientTimingTableA,x` at `$DAA1` | | `$DB2D-$DB38` | 12 | `IngredientTimingTableB` | 12 companion timing values stored in `Ram_045C` | `lda IngredientTimingTableB,x` at `$DAA7` | | `$DB39-$DB44` | 12 | `IngredientTimingTableC` | 12 companion timing values stored in `Ram_045D` | `lda IngredientTimingTableC,x` at `$DAAD` | | `$DBBB-$DBBC` | 2 | `EnemySpawnAttributeTable` | 2 sprite attribute bytes (`$20,$40`) chosen by the enemy's one- or two-player flag and stored in `Ram_0448,y` | `lda EnemySpawnAttributeTable,x` at `$DB97` | | `$DBBD-$DBBF` | 3 | `EnemySpawnTileTable` | 3 sprite tile bytes (`$F0,$00,$1C`) written into the OAM tile byte `Ram_0703,y` | `lda EnemySpawnTileTable,x` at `$DBA1` | | `$DBC0-$DBD6` | 23 | `EnemySpawnPositionTable` | 23 sprite Y coordinates (`$1C,$AC,$AC,$1C,$1C,$5C,$5C,$1C,$1C,$AC,$AC,$1C,$1C,$8C,$8C,$1C,$1C,$CC,$CC,$2C,$1C,$AC,$9C`) written into the OAM Y byte `Ram_0700,y`; the index is `4 × Ram_0402 + Ram_0462` | `lda EnemySpawnPositionTable,x` at `$DBB1` | | `$DD2E` | 1 | `StagePatternPointerLo` | Low byte of a table of 16-bit little-endian pointers to the stage's pattern data (the high bytes live in the inline bytes at `$DD29`) | `lda StagePatternPointerLo,y` at `$DCF9` | | `$DD2F-$DD60` | 50 | `StagePatternPointerHi` | High byte of the same table; the pointers are read as `lda ($34),y` at `$DD10` to fetch the stage pattern bytes | `lda StagePatternPointerHi,y` at `$DCFE` | | `$E150` | 1 | `SpriteHandlerPointerLo` | Low byte of a table of 16-bit little-endian code addresses, one per sprite attribute high nibble | `lda SpriteHandlerPointerLo,x` at `$E143` | | `$E151-$E641` | 1265 | `SpriteHandlerPointerHi` | High byte of the same table (`$E18D, $E1A5, $E1DE, $E20D, $E23C, $E327, …`); the pair is executed with `jmp ($35)` at `$E14D` | `lda SpriteHandlerPointerHi,x` at `$E148` | | `$E6A5-$E6AC` | 8 | `EnemyFallYPositionTable` | 8 falling Y positions, 16 pixels apart (`$D1,$C1,$B1,$A1,$91,$81,$71,$61`), used to move a falling enemy one ladder rung at a time. Named through an equate in `locals.inc` because the bytes sit inside an inline block | `lda EnemyFallYPositionTable,y` at `$E698` | | `$E70E-$E711` | 4 | `PepperSpriteYTableA` | 4 sprite Y positions (`$00,$60,$78,$90`) for the pepper in the "still" state, indexed by the object's direction (`Ram_0407 & 3`) | `lda PepperSpriteYTableA,y` at `$E6DD` | | `$E712-$E715` | 4 | `PepperSpriteYTableB` | 4 sprite Y positions (`$00,$62,$7A,$92`) for the pepper in the second state | `lda PepperSpriteYTableB,y` at `$E6EA` | | `$E716-$E719` | 4 | `PepperSpriteYTableC` | 4 sprite Y positions (`$00,$4C,$64,$7C`) for the pepper in the third state | `lda PepperSpriteYTableC,y` at `$E706` | | `$E871-$E88C` | 28 | `PowerUpScoreTable` | 28 ascending score thresholds (`$01,$08,$10,$18,…,$F8,$FE`); the routine at `$E829` walks the table backwards until `Ram_043F` matches an entry | `cmp PowerUpScoreTable,x` at `$E82E` | | `$E88D-$E8A8` | 28 | `PowerUpTimerTable` | 28 companion values (`$48,$44,$48,$44,…,$40,$3C,$38,$34`) — the countdown that starts when the score reaches the matching threshold | `lda PowerUpTimerTable,x` at `$E83A` | | `$E939-$E93E` | 6 | `StatusPanelPointerLo` | Low byte of a table of 16-bit little-endian pointers to the status-panel drawing routines | `lda StatusPanelPointerLo,x` at `$E8B4` | | `$E93F-$E944` | 6 | `StatusPanelPointerHi` | High byte of the same table | `lda StatusPanelPointerHi,x` at `$E8B9` | | `$E945-$E947` | 3 | `StatusPanelSpriteTileTable` | 3 sprite tile bytes (`$FA,$FC,$FE`) written into the last OAM sprite's tile byte `Ram_07F9` | `lda StatusPanelSpriteTileTable,y` at `$E8EB` | | `$E948-$E94D` | 6 | `StatusPanelIndexTable` | 6 small indices (`$00,$01,$02,$00,$01,$02`) selecting which of the three panel tiles a given player/status gets | `ldy StatusPanelIndexTable,x` at `$E8E8` and `$E91C` | | `$E94E-$E950` | 3 | `StatusPanelCountTable` | 3 counts (`$04,$05,$06`) written into `Ram_07FF` alongside the panel sprite | `lda StatusPanelCountTable,y` at `$E91F` | | `$E9F5-$E9FF` | 11 | `StatusBarRunLengthTable` | 11 run lengths (`$05,$10,$20,$30,$50,$10,$15,$20,$40,$50,$50`) driving the status-bar fill through `SelectPpuRunLength` | `lda StatusBarRunLengthTable,x` at `$E977` | | `$EA00-$EA19` | 26 | `PlayerStatusOffsetTable` | Per-player offsets into the status arrays: the first entry gives the offset of player 1's score (`$00,$03,$00`), the second that of player 2 (`$1B,$1C,$04`), then the remaining per-player slots (`$01,$02`, `$20,$21`, `$11`-`$19`, `$1A`, `$1D`, `$13`-`$17`) | `ldy PlayerStatusOffsetTable,x` at `$E960`, `$E999` and `$E9CB` | | `$EAD0-$EAD6` | 7 | `SpriteSlotOffsetTable` | 7 sprite-slot offsets in the OAM buffer (`$00,$04,$08,$0C,$10,$14,$18`), one per object | `ldy SpriteSlotOffsetTable,x` at `$EA5C`, `$EA6E`, `$EA80`, `$EA92`, `$EAA4` and `$EABF` | | `$EAD7-$EAD9` | 3 | `SpriteTileTableA` | 3 sprite tile bytes (`$1C,$58,$70`) for one enemy appearance | `lda SpriteTileTableA,y` at `$EA7D` | | `$EADA-$EADC` | 3 | `SpriteTileTableB` | 3 sprite tile bytes (`$88,$5A,$72`) for the next appearance | `lda SpriteTileTableB,y` at `$EA8F` | | `$EADD-$EADF` | 3 | `SpriteTileTableC` | 3 sprite tile bytes (`$8A,$5C,$74`) | `lda SpriteTileTableC,y` at `$EAA1` | | `$EAE0-$EAE2` | 3 | `SpriteTileTableD` | 3 sprite tile bytes (`$8C,$5E,$76`) | `lda SpriteTileTableD,y` at `$EA6B` | | `$EAE3-$EAE5` | 3 | `SpriteTileTableE` | 3 sprite tile bytes (`$8E,$F0,$F2`) | `lda SpriteTileTableE,y` at `$EA59` | | `$EAE6-$F23A` | 1877 | `StageLayoutData` | The stage's graphics and layout data: the tile rows, the platform and ladder layout and the sprite graphics for one stage. The first four bytes select a nametable region for the layout copy | `lda StageLayoutData,y` at `$EA53`; the value is passed to `ProcessGameState_E951` at `$EA56`, which walks the block with `SelectPpuRunLength` | | `$F23B` | 1 | `TitleSpritePointerLo` | Low byte of a table of 16-bit little-endian pointers to the title-screen sprite data | `lda TitleSpritePointerLo,y` at `$CAF1` | | `$F23C-$F246` | 11 | `TitleSpritePointerHi` | High byte of the same table | `lda TitleSpritePointerHi,y` at `$CAF6` | | `$F247-$F24C` | 6 | `TitleSpriteFlagTable` | 6 flag bytes (all `$01`) copied to `Ram_071E`, `Ram_0722` and `Ram_046D` | `lda TitleSpriteFlagTable,y` at `$C87E` | | `$F24D-$F252` | 6 | `TitleSpriteTileTable` | 6 sprite tile bytes (all `$1C`) written to the OAM tile byte `Ram_071D` | `lda TitleSpriteTileTable,y` at `$C88A` | | `$F253-$F258` | 6 | `TitleSpriteXTable` | 6 sprite X positions (`$78,$78,$78,$78,$A8,$78`) written to the OAM X byte `Ram_071F` | `lda TitleSpriteXTable,y` at `$C899` | | `$F259-$F477` | 543 | `TitleSpriteYAndLayoutData` | The first 6 bytes are the title sprites' Y positions (`$AC,$BC,$CC,$8C,$CC,$AC`, the same values as `AttractModeSpriteTileTable`); the rest of the block holds the title-screen layout and graphics data | `lda TitleSpriteYAndLayoutData,y` at `$C8A8` (the Y position); the remainder is read through the same base by the layout code | | `$F6DC-$F6E3` | 8 | `BitShiftTable` | 8 powers of two, descending (`$80,$40,$20,$10,$08,$04,$02,$01`) — the shift amounts used to mask a bit out of a sprite's state byte | `lda BitShiftTable,y` at `$F6D0` | | `$F938` | 1 | `ObjectHandlerPointerLo` | Low byte of a table of 16-bit little-endian code addresses, one per object handler | `lda ObjectHandlerPointerLo,y` at `$F970` | | `$F939-$F96D` | 53 | `ObjectHandlerPointerHi` | High byte of the same table (`$F8F8`, `$F865`, `$F865`, `$F880`, `$F89D`, `$F89D`, `$F8A4`, `$F8AB`, `$F8F5`, …) | `lda ObjectHandlerPointerHi,y` at `$F975` | | `$F98A` | 1 | `EnemySpeedPointerLo` | Low byte of a table of 16-bit little-endian pointers to the per-enemy speed tables | `lda EnemySpeedPointerLo,y` at `$F7FA` | | `$F98B-$F9A1` | 23 | `EnemySpeedPointerHi` | High byte of the same table | `lda EnemySpeedPointerHi,y` at `$F7F4` | | `$F9A2` | 1 | `SoundEffectPointerLo` | Low byte of a table of 16-bit little-endian pointers to the sound-effect data, indexed by `PendingSoundCommand` ($90) | `lda SoundEffectPointerLo,y` at `$F5C3` | | `$F9A3-$FD23` | 897 | `SoundEffectPointerHi` | High byte of the same table; the block also holds the sound-effect data the pointers select | `lda SoundEffectPointerHi,y` at `$F5C8` | Three further tables are named through equates in `locals.inc` rather than by a label in `prg.s`, because their bytes sit in the middle of an inline block: | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$D11F-$D12C` | 14 | `ObjectScriptPointerTable` | 7 × 16-bit little-endian pointers to per-object byte scripts (`$6038`, `$D12D`, `$D13A`, `$D15B`, `$D168`, `$D181`, `$D182`); each script is a byte stream terminated by `$FF` | `lda ObjectScriptPointerTable,y` at `$D0ED` and `$D0F3`, with `Y` = 2 × the object index | | `$DD28-$DD29` | 2 | `StagePatternPointerLoTable` | Low bytes of the stage-pattern pointer table; the high bytes are the inline bytes at `$DD29` | `lda StagePatternPointerLoTable,y` at `$DCEF` | | `$E6A5-$E6AC` | 8 | `EnemyFallYPositionTable` | 8 falling Y positions, 16 pixels apart (`$D1,$C1,$B1,$A1,$91,$81,$71,$61`) | `lda EnemyFallYPositionTable,y` at `$E698` | ### Inline data blocks These are the bytes that sit inside a routine and are consumed by the `jsr` immediately above them (the routine pulls its arguments out of the return address). They are listed so that the inventory covers every byte of the PRG that is not an instruction. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C04D` | 1 | inline after `Flow_C04C` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$C12E` | 49 | inline after `Flow_C0F6` | 4 `.byte` row(s) of inline data that follow the call | `SetMainState` at `$C0F8` | | `$C225` | 53 | inline after `Flow_C1BF` | 4 `.byte` row(s) of inline data that follow the call | `SetMainState` at `$C1EF` | | `$C5F5` | 170 | inline after `ProcessGameState_C54B` | 11 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$C6B3` | 25 | inline after `ProcessGameState_C69C` | 2 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$C955` | 7 | inline after `Flow_C954` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$CAA0` | 7 | inline after `ProcessGameState_CA87` | 1 `.byte` row(s) of inline data that follow the call | `ProcessGameState_CE86` at `$CA8E` | | `$CAC0` | 7 | inline after `ProcessGameState_CAA7` | 1 `.byte` row(s) of inline data that follow the call | `ProcessGameState_CE86` at `$CAAE` | | `$CB43` | 48 | inline after `Flow_CB1A` | 3 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$CBB5` | 12 | inline after `Flow_CBA4` | 1 `.byte` row(s) of inline data that follow the call | `ProcessGameState_CE86` at `$CBA4` | | `$CC7A` | 147 | inline after `Flow_CBCD` | 10 `.byte` row(s) of inline data that follow the call | `ProcessGameState_CE86` at `$CBDE` | | `$CE83` | 19 | inline after `ProcessGameState_CE6B` | 2 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$CE97` | 12 | inline after `Flow_CE8E` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$CEFA` | 45 | inline after `ProcessGameState_CEC8` | 3 `.byte` row(s) of inline data that follow the call | `LoadPalette` at `$CECE` | | `$D0D4` | 384 | inline after `ProcessGameState_CF54` | 24 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$D181` | 110 | inline after `Flow_D11C` | 7 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$D1B1` | 32 | inline after `TransformValue_D18F` | 2 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$DD2A` | 4 | inline after `Flow_DD29` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$E0B0` | 39 | inline after `Flow_E08F` | 3 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$E0D8` | 12 | inline after `Flow_E0D7` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$E6A6` | 8 | inline after `Flow_E697` | 1 `.byte` row(s) of inline data that follow the call | `StorePendingSoundCommand` at `$E6A0` | | `$E7D1` | 109 | inline after `Flow_E739` | 7 `.byte` row(s) of inline data that follow the call | the routine it follows | | `$F5F9` | 11 | inline after `Flow_F5E1` | 1 `.byte` row(s) of inline data that follow the call | `TransformValue_F6BA` at `$F5F3` | | `$FD8D` | 16 | inline after `ProcessGameState_FD83` | 1 `.byte` row(s) of inline data that follow the call | `LoadPalette` at `$FD89` | | `$FFF6` | 4 | inline after `SetDefaultScroll` | 1 `.byte` row(s) of inline data that follow the call | the routine it follows | ## 13. Notes - The disassembly is organized as a state machine driven by `MainGameState` ($16), with the dispatch table at CPU $C000–$C00D. - The NMI is the sole frame-sync source; the main loop (`Flow_FD4F`) blocks on `WaitForNmi` before dispatching to the current state handler. - Gameplay updates run on an 8-frame divider (`Zp_15` at $15), not every frame. - Audio is updated both from the NMI path (`ProcessGameState_F493` when `GameMode != 0`) and from state-specific code paths. - Many routines are still named `Flow_Cxxx` or `ProcessGameState_Cxxx` based on their CPU address, reflecting the automated disassembly origins. Their semantic names are provisional. **Every data table, however, now carries a ROM-specific name** — the old `LookupTable_Cxxx` placeholders are gone; see the *Pass 3 data-table inventory* above. - The audit comments on every instruction carry the wrong CPU base: they read `CPU:$8000` where the PRG is mapped at `$C000`, so every address was out by `$4000`. The trail has been rebuilt with `_tools/repair_audit_comments.py --base 0xC000`, which restores the repository's canonical `; CPU:$Cxxx PRG:xxxx AA BB` form. The PRG offsets were already correct and are unchanged; the build is still byte-identical. - Build verification: `make` in this folder produces `reconstructed.nes` that is **byte-identical** to `rom.nes`. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry.