Moves Wild Gunman (U) from _wip/ to Wild Gunman (U)/ with full ROM build: - Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments) - Updates Makefile to build and verify reconstructed.nes vs rom.nes - Adds rom.nes copy + copilot/rom.nes symlink per repo convention - Verified byte-identical build Pass 1 complete: full code/data classification (~20% code / ~80% data) Pass 2 partial: routine headers for all labels, semantic naming for key routines (Reset, NmiHandler, MainStateDispatch, MainLoopBody, etc.), zero-page aliases in locals.inc Updates root OVERVIEW.md: - Removes Wild Gunman from _wip skeleton table (23 -> 22 skeletons) - Adds Wild Gunman as #13 in numbered inventory (BYTE-IDENTICAL, Pass 2) - Updates summary count from 12 to 13 projects
6.4 KiB
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 | Horizontal |
| PRG bank start | CPU $C000 |
| Source file | copilot/prg.s (~3,000 lines with headers) |
| Output | reconstructed_prg.bin (16,384 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 prg.s with ca65, links with ld65, and verifies against prg.bin.
Verification result: BYTE-IDENTICAL — reconstructed_prg.bin matches prg.bin exactly (all 16,384 bytes), and prg.bin matches the PRG bank of Wild Gunman (U) [!].nes.
3. Pass Status
| Pass | Status | Description |
|---|---|---|
| Pass 1 | Complete | Code/data split — entire PRG bank classified as traced code (~20%) or explicit .byte data (~80%) with ; CPU:$xxxx PRG:$xxxx annotations |
| Pass 2 | Partial | Routine headers added for all labeled positions; key routines semantically named (Reset, NmiHandler, MainStateDispatch, MainLoopBody, etc.); zero-page aliases in locals.inc |
| Pass 3 | Partial | Some data tables referenced by name in code; deeper table naming is future work |
4. Reset — CPU $FFFC → $C000
Reset ($C000)
On power-on or reset:
CLD— clear decimal mode;SEI— disable maskable interrupts.- Wait for PPU warm-up vblank — poll
$2002bit 7 until clear (bplloop at $C002–$C005). STX $2000 / STX $2001— disable PPU rendering and NMI with X=0.DEX / TXS— initialize stack pointer to $FF.LDX $05EC— read OAM base value;LDY #$06 / STY $01— set up a 6-page counter.- Clear RAM — zero pages $0000–$05FF via indirect store loop (
STA (Zp00),Ywith page increment). LDX #$5F / STX $05EC— set OAM base to $5F.JSR ResetInitSub1($C20B) andJSR ResetInitSub2($C21B) — initialization subroutines.- Set scroll to 0 (
STA $2005twice with Y=0). LDY #$01 / STY $30— setGameMode= 1.LDA #$0F / STA $4015— enable all APU channels.JSR $FFF7— jump via vector to sound init at $F40F.LDA #$90 / STA $2000— set PPUCTRL = $90 (NMI enabled, $2000 base).STA Zp08($08 = $90);LDA #$02 / STA Zp09($09 = $02).- Copy 12 bytes from $C844 to $05F0 (sprite/OAM data table).
- Main loop (
Label_C07Cat $C07C): checkNmiPhase($20); if nonzero, jump to $C061; otherwise callMainLoopBody($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:
- Save registers (
PHP / PHA / TXA / PHA / TYA / PHA). - Set OAMADDR to 0 (
STA $2003), trigger OAM DMA (STA $4014with value 2 → $0200–$02FF). - Check
NmiFlag($22) — if nonzero, skip to $C0A6 (restore registers, RTI). - Game logic — call
NmiGameLogic3($C29F),NmiGameLogic1($C233),NmiGameLogic5($C3AC),NmiGameLogic2($C265). LDY #$01 / STY NmiFlagandSTY NmiPhase— set both flags to 1.- 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
cd "_wip/Wild Gunman (U)/copilot"
make # → ca65 + ld65 + cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
10. Notes
- The PRG is approximately 20% code / 80% data. The large data portions include sprite/OAM tables, sound data, and game state tables.
- The NMI handler drives the per-frame game loop; the main loop (
Resetat $C07C) spins waiting for NMI phase changes. GameMode($30) selects between different game states/modes.- The vectors at $FFF4 and $FFF7 are indirect jump targets to sound routines at $F372 and $F40F respectively.
- IRQ vector points to $C000 (Reset) and is effectively unused.
11. Next Steps
- Promotion — move to a top-level
Wild Gunman (U)/folder, addheader.s/chr.s+ full-ROMmaketarget (as e.g.Burger Time (U)), then join the rootOVERVIEW.mdnumbered inventory. - Pass 2/3 continuation — semantically rename remaining
Label_Cxxxxroutines, name data tables (sprite data at $C844, sound tables in $E000–$FFFF range), and refine zero-page aliases.