Files
disasm/Wild Gunman (U)/OVERVIEW.md
T
tmerritt c79a52e354 Promote Wild Gunman (U) from _wip to top-level project
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
2026-10-16 18:51:15 -04:00

6.4 KiB
Raw Blame History

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:

  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

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 (Reset at $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, add header.s / chr.s + full-ROM make target (as e.g. Burger Time (U)), then join the root OVERVIEW.md numbered inventory.
  • Pass 2/3 continuation — semantically rename remaining Label_Cxxxx routines, name data tables (sprite data at $C844, sound tables in $E000–$FFFF range), and refine zero-page aliases.