# Hogan's Alley (JU) — Game Flow from Power-On This document describes the execution flow of the disassembled Hogan's Alley (JU) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `copilot/prg.s` in the repository. The rebuild is verified **byte-identical** to the original `prg.bin` at the PRG level. --- ## 1. Reset Vector — CPU $FFFC → $C000 **`Reset`** ($C000–$C085) On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization: | Step | Code | Purpose | |------|------|---------| | 1–2 | `cld` / `sei` | Clear decimal mode, disable interrupts | | 3–6 | Poll `$2002` twice (`bpl *-3`) | Wait for two full VBlank periods (PPU warm-up) | | 7–8 | `ldx #$00` / `txs` | Initialize stack pointer to $01FF | | 9–21 | Clear RAM $0000–$07FF | Uses Zp00/Zp01 as 16-bit pointer; clears 8 pages of 256 bytes | | 22–23 | Warm-start signature check | Loads `$05EC` into X; if non-zero, skips full clear and sets X=$5F | | 24 | `jsr $C221` | Initialize PPU/APU registers | | 25 | `jsr $C231` | Additional hardware init | | 26–27 | PPU scroll init | Write $00 to `$2005` twice (X/Y scroll) | | 28 | `lda #$0F` / `sta $4015` | Enable all APU channels | | 29 | `jsr $FFF7` | Call audio engine init (trampoline to $F984) | | 30 | `lda #$90` / `sta $2000` / `sta Zp08` | PPUCTRL: NMI on, sprites at $0000, BG at $1000, + $90 shadow in Zp08 | | 31 | `lda #$02` / `sta Zp09` | Zp09 = $02 | | 32–36 | Copy sprite data | Copy 12 bytes from $E36A to $05F0 (OAM staging area) | | 37 | `bmi Label_C07E` | Branch to main loop (always taken after copy) | **Warm-start signature:** Byte at `$05EC` survives RAM clear. On soft reset, if non-zero, the game skips the full 2 KB clear and initializes X=$5F instead of X=$00. --- ## 2. Interrupt Vectors — CPU $FFFA–$FFFF Located at the end of the bank (PRG offset $3FFA): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C088 | `NmiHandler` | | RESET | $C000 | `Reset` | | IRQ | $C000 | `Reset` (reused — unused placeholder) | --- ## 3. NMI / Frame Heartbeat — `NmiHandler` ($C088) The NMI fires once per frame and drives the frame pacing. It implements a two-phase frame structure: ``` NmiHandler ($C088): php / pha / txa / pha / tya / pha ; preserve registers lda #$00 / sta $2003 ; OAMADDR = 0 lda #$02 / sta $4014 ; OAM DMA from $0200 lda Zp22 ; NMI phase flag bne Label_C0AB ; if phase 1: skip game logic jsr $C2BD ; update game objects jsr $C24E ; update targets jsr $C3A8 ; update shots jsr $C282 ; update sprites jsr $C3D0 ; update HUD/score Label_C0AB: ; phase 1 entry / phase 0 exit ldy #$01 / sty Zp22 / sty Zp20 ; set phase=1, clear main flag pla / tay / pla / tax / pla / plp / rti ; restore, return ``` **Phase 0 (Zp22=0):** Full game logic update — objects, targets, shots, sprites, HUD. **Phase 1 (Zp22=1):** Minimal — only OAM DMA, then returns. The main loop toggles Zp22. --- ## 4. Main Game Loop — $C07E After initialization, control reaches the per-frame loop at **$C07E**: ``` Label_C07E: lda Zp20 ; main loop flag (set by NMI phase 0) bne Label_C065 ; if set, run frame logic jsr $C6BD ; attract/demo mode logic jmp $C07E ; loop ``` When NMI phase 0 completes, it sets **Zp20 = $01** (at $C076 in `Label_C070`). The main loop sees this, branches to **`Label_C065`** ($C065), which dispatches based on game state: ``` Label_C065: lda Zp24 ; game mode/state beq Label_C06D ; if 0: audio-only frame (jsr $FFF4) lda Zp26 ; sub-state beq Label_C070 ; if 0: gameplay frame Label_C06D: jsr $FFF4 ; audio engine update (trampoline to $FFD4) rts Label_C070: jsr $C337 ; controller input jsr $C0B8 ; state dispatcher inc Zp2F ; frame counter lda #$00 / sta Zp20 / sta Zp22 ; clear flags for next frame ``` **`Label_C0B8` ($C0B8)** — State dispatcher: ``` lda Zp24 beq Label_C0BF ; if game mode 0: title/gameplay jmp $C156 ; else: other modes (game over, etc.) ``` **`Label_C0BF` ($C0BF)** — Title/Gameplay dispatcher: ``` lda Zp16 / and #$10 / beq Label_C11A ; check bit 4 of Zp16 ldy $05FC / bne Label_C11F ; check $05FC iny / sty $05FC ldy Zp25 / cpy #$07 / beq Label_C106 ; state 7 cpy #$03 / beq Label_C0E4 ; state 3 cpy #$04 / beq Label_C0E4 ; state 4 cpy #$0A / beq Label_C0E4 ; state A cpy #$0F / bne Label_C11F ; state F ``` States 3, 4, A → **`Label_C0E4`** (gameplay init); State 7 → **`Label_C106`**; others → **`Label_C11F`** (continue gameplay). --- ## 5. Key Subsystems ### 5.1 Gameplay Initialization — `Label_C0E4` ($C0E4) ``` ldx #$05 Label_C0E6: lda Zp2A,x / sta $0430,x ; copy Zp2A–Zp2F to $0430–$0435 dex / bpl Label_C0E6 jsr $C14A ; copy stack page $01FF to $0217 jsr $CDB4 ; initialize round data lda #$25 / sta Zp45 ; set timer jsr $CE4B ; spawn targets lda Zp25 / sta Zp1C ; save state lda #$07 / sta Zp25 ; set state = 7 jmp $C10E ``` ### 5.2 State 7 Handler — `Label_C106` ($C106) ``` lda #$11 / sta Zp25 ; set state = $11 lda #$30 / sta Zp1D ; set timer ``` ### 5.3 Main Gameplay Loop — `Label_C10E` ($C10E) ``` jsr $C746 ; update targets jsr $D291 ; update shots jsr $FFF4 ; audio engine jmp $D2B4 ; update sprites/OAM ``` ### 5.4 Controller Input — `$C337` Reads controller via `$4016` (strobe + shift), stores button state in Zp16/Zp17. ### 5.5 Audio Engine — `$FFF4` / `$FFF7` Trampolines at $FFF4 (update) and $FFF7 (init) jump to banked code at $F9D4/$F984. ### 5.6 Target System — `$C746`, `$CDB4`, `$CE4B` Manages the 5–6 shootable targets (cans, birds, etc.) — position, state, scoring. ### 5.7 Shot System — `$D291`, `$D2B4` Player shots: tracking, collision with targets, scoring. --- ## 6. Memory Map (Key Zero Page / RAM) | Address | Name (from `locals.inc`) | Use | |---------|--------------------------|-----| | $00–$01 | `Zp00` / `Zp01` | 16-bit pointer for RAM clear (Reset) | | $08 | `Zp08` | PPUCTRL shadow ($90) | | $09 | `Zp09` | Init value $02 | | $16 | `Zp16` | Controller buttons (bit 4 = start pressed) | | $1C | `Zp1C` | Saved game state | | $1D | `Zp1D` | Timer/counter | | $20 | `Zp20` | Main loop flag (set by NMI phase 0) | | $22 | `Zp22` | NMI phase flag (0=game logic, 1=OAM only) | | $24 | `Zp24` | Game mode (0=title/play, non-zero=other) | | $25 | `Zp25` | Sub-state (3,4,7,A,F,11, etc.) | | $26 | `Zp26` | Sub-state flag | | $29 | `Zp29` | Reset signature ($B6) | | $2A–$2F | `Zp2A`–`Zp2F` | Gameplay params, copied to $0430 | | $2F | `Zp2F` | Frame counter | | $30 | `Zp30` | Frame ready flag (set by NMI) | | $45 | `Zp45` | Gameplay timer ($25 init) | | $0430–$0435 | — | Gameplay parameter shadow | | $05EC | — | Warm-start signature byte | | $05F0–$05FB | — | OAM staging (12 bytes from $E36A) | | $05FC | — | Title screen state | | $06F0 | — | Reset signature mirror | | $4014 | OAMDMA | OAM DMA trigger | | $4015 | APUSTATUS | APU channel enable | | $4016 | JOY1 | Controller 1 strobe/read | --- ## 7. Object Pools / Data Structures | Region | Purpose | |--------|---------| | $0430–$0435 | Gameplay parameters (target positions, timers) | | $05F0–$05FB | Sprite/OAM data for 4–6 objects | | $0400–$04FF | Sprite data (positions, states) | | $0200–$02FF | OAM buffer (DMA source) | | $E36A–$E375 | Initial sprite data (copied to $05F0 at reset) | --- ## 8. Summary of Execution Flow ``` Power On / Reset │ ▼ Reset ($C000) ────► cld/sei, wait 2 VBlanks, init stack │ ├─ Clear $0000–$07FF (8 pages via Zp00/Zp01) │ ├─ Check $05EC warm-start signature │ ├─ Zero: full clear, X=$00 │ └─ Non-zero: X=$5F (skip clear) │ ├─ jsr $C221, $C231 (PPU/APU init) ├─ PPU scroll = 0,0 ├─ APU enable ($4015=$0F) ├─ jsr $FFF7 (audio init → $F984) ├─ PPUCTRL = $90 (NMI on) ├─ Copy $E36A→$05F0 (12 bytes sprite data) │ ▼ Main Loop ($C07E) ─────────────────────────────────────────┐ │ │ ├─ lda Zp20 (NMI phase 0 flag) │ │ ├─ Zero: jsr $C6BD (attract/demo), loop │ │ └─ Non-zero: branch to Label_C065 │ │ │ ▼ │ Label_C065 ($C065) ───► lda Zp24 (game mode) │ │ ├─ 0: jsr $FFF4 (audio only), rts │ │ └─ Non-zero: lda Zp26 │ │ ├─ 0: Label_C070 (gameplay frame) │ │ └─ Non-zero: jsr $FFF4 (audio), rts │ │ │ ▼ │ Label_C070 ($C070) ──► jsr $C337 (controller) │ │ jsr $C0B8 (state dispatch) │ │ inc Zp2F (frame counter) │ │ clear Zp20, Zp22 │ │ │ ▼ │ Label_C0B8 ($C0B8) ──► lda Zp24 │ │ ├─ 0: Label_C0BF (title/play dispatcher) │ │ └─ Non-zero: jmp $C156 (other modes) │ │ │ ▼ │ Label_C0BF ($C0BF) ──► state machine on Zp25 │ │ ├─ 3,4,A: Label_C0E4 (gameplay init) │ │ ├─ 7: Label_C106 (state 7 handler) │ │ └─ else: Label_C11F (continue) │ │ │ ▼ │ Gameplay states (3,4,A→7→11→...) ──► Label_C10E │ │ jsr $C746 (targets) │ │ jsr $D291 (shots) │ │ jsr $FFF4 (audio) │ │ jmp $D2B4 (sprites) │ │ │ ▼ (loop back to $C07E) │ NMI ($C088) [once per frame] ├─ Preserve registers ├─ OAM DMA ($4014 from $0200) ├─ lda Zp22 (phase flag) │ ├─ 0: jsr $C2BD (objects), $C24E (targets), $C3A8 (shots), │ │ $C282 (sprites), $C3D0 (HUD) │ └─ 1: skip game logic ├─ ldy #$01 / sty Zp22 / sty Zp20 (toggle phase, signal main) ├─ Restore registers, rti ``` --- ## 9. Naming and Residue **Pass 1 complete** — 9,810 lines, 568 `Label_` definitions, canonical audit comments, 41.8% code. **Pass 2 outstanding** — Root inventory: *needs CDL reclass*. The 55 seeded routine entries from table references include `$E9F1` which was a false positive (data table base, not dispatch) and was forced back to data 2026-10-19. 31 of the remaining 54 have no direct `jsr`/`jmp` reference. **Pass 3 outstanding** — No semantic zero-page aliases; `locals.inc` holds 171 neutral `ZpXX` names. **Indirect dispatch** (not followed statically): - `jmp ($0014)` at $C360 - `jmp ($0610)` at $F927 - `jmp ($0612)` at $F941 **Promotion outstanding** — Add `header.s`/`chr.s` + HEADER/PRG/CHR `linker.cfg`, switch `make` to whole-ROM `cmp`, move `_tools` paths from `../../../_tools/` to `../../_tools/`. Build verification: `make` in `copilot/` produces `reconstructed_prg.bin`, compared against `prg.bin` (PRG byte-identical).