# 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 `copilot/prg.s` in the repository. The rebuild is verified **byte-identical** to the original `burger_time.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 | `copilot/prg.s` (9,323 lines), `copilot/header.s`, `copilot/chr.s` | | Output | `burger_time_rebuilt.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 `copilot/` assembles `header.s`, `prg.s`, and `chr.s`, links them via `ld65`, and verifies the result against `burger_time.nes`. **Verification result: BYTE-IDENTICAL** — headers, CHR, and all PRG bytes match exactly. Note: the Makefile references `burger_time.nes`, while the actual ROM file is named `Burger Time (U) [!].nes`. A symlink `burger_time.nes → "../Burger Time (U) [!].nes"` is required before building. --- ## 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 `MainStateDispatchTable` ($C000) - High byte from `LookupTable_C001` ($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) ``` --- ## 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 named `Flow_Cxxx` or `ProcessGameState_Cxxx` based on their CPU address, reflecting the automated disassembly origins. Semantic names are provisional. - Build verification: `make` in `copilot/` produces `burger_time_rebuilt.nes` that is **byte-identical** to `burger_time.nes` (requires a symlink named `burger_time.nes` pointing to the actual ROM file due to spaces/special characters in the original filename).