# Wild Gunman (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Wild Gunman (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 `prg.bin`. --- ## 1. ROM and Memory Layout | Property | Value | |----------|-------| | Format | iNES | | PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) | | CHR | 1 × 8 KB | | Mirroring | Vertical | | PRG bank start | CPU $C000 | | Source file | `copilot/prg.s` (11,544 lines with routine headers) | | Output | `reconstructed.nes` (24,592 bytes) | ### Interrupt vectors (CPU $FFFA–$FFFF) | Vector | Address | Handler | |--------|---------|---------| | NMI | $C086 | `NmiHandler` | | RESET | $C000 | `Reset` | | IRQ / BRK | $C000 | Reuses `Reset` | --- ## 2. Build Status `make` in `copilot/` assembles `header.s`, `prg.s` and `chr.s` with `ca65`, links them with `ld65`, and compares the result against `rom.nes` (a relative symlink to `../Wild Gunman (U) [!].nes`). **Verification result: BYTE-IDENTICAL** — `reconstructed.nes` (24,592 bytes: 16-byte header + 16,384-byte PRG + 8,192-byte CHR) matches `rom.nes` exactly, so the header and CHR are proven, not just the PRG bank. --- ## 3. Pass Status | Pass | Status | Description | |------|--------|-------------| | Pass 1 | Complete | Code/data split — 3,956 traced instructions / 8,085 bytes (49.3%); the rest is explicit `.byte` data with `; CPU:$xxxx PRG:xxxx` audit comments | | Pass 2 | Complete | Routine headers on every entry point; all 733 labels carry names (zero `Label_Cxxxx`/`SubCxxxx` remain); zero-page aliases in `locals.inc` | | Pass 3 | Complete | Data tables named: `SpriteAttributeTable` ($C6AC), `NoteVolumeTable` ($F3EF), `VolumeEnvelopeTable` ($F48F), `SpritePositionTable` ($F77C); the OAM sprite block at $C844 is documented in-place | --- ## 4. Reset — CPU $FFFC → $C000 **`Reset`** ($C000) On power-on or reset: 1. `CLD` — clear decimal mode; `SEI` — disable maskable interrupts. 2. **Wait for PPU warm-up vblank** — poll `$2002` bit 7 until clear (`bpl` loop at $C002–$C005). 3. `STX $2000 / STX $2001` — disable PPU rendering and NMI with X=0. 4. `DEX / TXS` — initialize stack pointer to $FF. 5. `LDX $05EC` — read OAM base value; `LDY #$06 / STY $01` — set up a 6-page counter. 6. **Clear RAM** — zero pages $0000–$05FF via indirect store loop (`STA (Zp00),Y` with page increment). 7. `LDX #$5F / STX $05EC` — set OAM base to $5F. 8. `JSR ResetInitSub1` ($C20B) and `JSR ResetInitSub2` ($C21B) — initialization subroutines. 9. Set scroll to 0 (`STA $2005` twice with Y=0). 10. `LDY #$01 / STY $30` — set `GameMode` = 1. 11. `LDA #$0F / STA $4015` — enable all APU channels. 12. `JSR $FFF7` — jump via vector to sound init at $F40F. 13. `LDA #$90 / STA $2000` — set PPUCTRL = $90 (NMI enabled, $2000 base). 14. `STA Zp08` ($08 = $90); `LDA #$02 / STA Zp09` ($09 = $02). 15. Copy 12 bytes from $C844 to $05F0 (sprite/OAM data table). 16. **Main loop** (`Label_C07C` at $C07C): check `NmiPhase` ($20); if nonzero, jump to $C061; otherwise call `MainLoopBody` ($C573) and loop forever. **Key point:** Like several other games in this repo, the main loop is essentially driven by NMI. The `Reset` code sets up hardware and enters a loop that waits for NMI phase changes. --- ## 5. NMI / Frame Heartbeat — `NmiHandler` ($C086) The NMI fires once per frame: 1. **Save registers** (`PHP / PHA / TXA / PHA / TYA / PHA`). 2. Set OAMADDR to 0 (`STA $2003`), trigger OAM DMA (`STA $4014` with value 2 → $0200–$02FF). 3. Check `NmiFlag` ($22) — if nonzero, skip to $C0A6 (restore registers, RTI). 4. **Game logic** — call `NmiGameLogic3` ($C29F), `NmiGameLogic1` ($C233), `NmiGameLogic5` ($C3AC), `NmiGameLogic2` ($C265). 5. `LDY #$01 / STY NmiFlag` and `STY NmiPhase` — set both flags to 1. 6. **Restore registers** (`PLA / TAY / PLA / TAX / PLA / PLP / RTI`). --- ## 6. Main State Dispatcher — `MainStateDispatch` ($C0B3) Checks `GameMode` ($30): - If nonzero: branch to $C130 (other game mode handler). - If zero: check `Ctrl1` ($16) bit 4 (`AND #$10`); if zero, go to $C10A. - Otherwise, read `OAMBase` ($05FC) into Y; if nonzero, go to $C10F. - Continue with startup sequence. --- ## 7. Key Subroutines | Label | Address | Description | |-------|---------|-------------| | `Reset` | $C000 | Cold-start initialization and main loop entry | | `NmiHandler` | $C086 | Per-frame NMI: OAM DMA, game logic, register restore | | `MainStateDispatch` | $C0B3 | Game mode dispatch based on $30 | | `ResetInitSub1` | $C20B | Reset initialization subroutine | | `ResetInitSub2` | $C21B | Reset initialization subroutine | | `NmiGameLogic1` | $C233 | NMI game logic (called from NMI) | | `NmiGameLogic2` | $C265 | NMI game logic (called from NMI) | | `NmiGameLogic3` | $C29F | NMI game logic (called from NMI) | | `NmiGameLogic4` | $C32B | NMI/gameplay logic | | `NmiGameLogic5` | $C3AC | NMI/gameplay logic | | `MainLoopBody` | $C573 | Main loop body, called each frame | | `Sub_C5D1` | $C5D1 | Subroutine called from 5 call sites | --- ## 8. Zero-Page Aliases (`locals.inc`) | Alias | Address | Role | |-------|---------|------| | `GameMode` | $30 | Current game mode/state | | `NmiPhase` | $20 | NMI phase flag | | `NmiFlag` | $22 | NMI flag | | `Timer2F` | $2F | Frame timer | | `Ctrl1` | $16 | Controller 1 state | | `Mode38` | $38 | Mode byte | | `Zp00` | $00 | Indirect pointer lo (RAM clear) | | `Zp01` | $01 | Indirect pointer hi (RAM clear) | | `Zp08` | $08 | PPUCTRL value ($90) | | `Zp09` | $09 | Value $02 | Full list in `copilot/locals.inc`. --- ## 9. Build ```bash cd "Wild Gunman (U)/copilot" make # → ca65 + ld65 + cmp reconstructed.nes rom.nes → "Byte-identical ROM verified." ``` --- ## 10. Notes - The PRG is 49.3% code / 50.7% data. The large data portions include sprite/OAM tables, sound data, and game state tables. - The NMI handler drives the per-frame game loop; `MainLoopBody` ($C573) spins on the NMI phase flag set by `NmiHandler` ($C086, phase check at $C07C). - `GameMode` ($30) selects between different game states/modes. - Stray jump stubs at $FFF4 (`jmp $F372`) and $FFF7 (`jmp $F40F`) sit just before the real vector words; they are not vectors themselves. The actual $FFFA–$FFFF words are $C086 / $C000 / $C000 (NMI / RESET / IRQ). - IRQ vector points to $C000 (Reset) and is effectively unused. --- ## 11. Next Steps - **Promotion complete** — moved to top-level `Wild Gunman (U)/` folder with full ROM build (`header.s`, `chr.s`, `linker.cfg`, `Makefile`). - **Optional deeper Pass 3 work** — extract the sprite block at $C844 into its own labeled `.byte` block (currently documented in-place as a comment), and refine remaining zero-page aliases. The named sound tables (`NoteVolumeTable` $F3EF, `VolumeEnvelopeTable` $F48F, `SpritePositionTable` $F77C) already cover the $F000-range data.