# 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 interpreted: every one of the 23 data blocks in the PRG is listed in the **Pass 3 data-table inventory** below with its structure and its consumer. Two blocks are genuine named data (`InitialSpriteDataTable` at $C844, `NoteVolumeTable` at $F3EF, `VolumeEnvelopeTable` at $F48F); the rest are recorded as unreached code or unreferenced data - the earlier `SpritePositionTable` name at $F77C was wrong and has been removed, because those bytes are code | --- ## 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 | | `WaitForNmiPhase` | $C5D1 | Clears the NMI phase and flag, spins until the NMI raises the phase byte, then calls the NMI vector | --- ## 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`). - **Re-split Pass 1 with a CDL capture.** The single biggest open item: the tracer reached 49.3 % of the PRG, so roughly half of the ROM — including the whole sound engine at `$DEED-$F371` and `$F77C-$FFF3` — is filed as data. A Mesen CDL capture of a full round would let the tracer reach the sound routines and would turn most of the "data" blocks in the inventory above back into code, which would also let the sound tables at `$F3EF` and `$F48F` be given a real consumer. - **Refine the remaining zero-page aliases.** --- ## Pass 3 data-table inventory All 23 data blocks in the 16 KB PRG are listed below. Wild Gunman's Pass 1 tracer reached only about half of the ROM, so most of the blocks the file marks as "data" are in fact **code the tracer never reached** - they are recorded as such rather than being given a plausible-sounding name. Two blocks are genuine data and are named; three more are small data fragments inside unreached code. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C12A-$C12F` | 6 | (unnamed) | **Unreferenced data.** Six bytes (`47 C2 81 DB DE C5`) inside the unreached code at `$C0F0-$C12F`; the ROM does not settle what they are | no instruction in this dump references it | | `$C20C-$C20C` | 1 | (unnamed) | **Unreferenced data.** A single `$02` byte sitting between `ResetInitSub1` at `$C20B` and `ResetInitSub1_Sub1` at `$C210` | no instruction in this dump references it | | `$C422-$C4A9` | 136 | (unnamed) | **Reached code, mis-filed as data.** `ControllerSub` at `$C422` and the routines that follow it (the controller-reader helpers) - the bytes decode as instructions (`clc / adc #$01 / and #$0F / asl / asl / tax ...`) | `ControllerSub` at `$C422` is called by `ReadController1` at `$C411`; the analyzer's `lda $C472,x` style operands inside this block are code operands, not table reads | | `$C4CB-$C4EC` | 34 | (unnamed) | **Reached code, mis-filed as data.** `ControllerSub2` at `$C4CB` - the button-edge detector (`jsr $C4ED / cmp #$01 / bne ... / eor #$0A / ...`) | reached from `ControllerSub` at `$C422` | | `$C4FC-$C572` | 119 | (unnamed) | **Reached code, mis-filed as data.** `ControllerSub4` at `$C4FC` - the analogue-stick reader that counts how far the stick is pushed (`lda #$00 / sta $04 / clc / lda $00 / adc #$10 / ...`) | reached from the controller helpers at `$C422`-`$C4CB` | | `$C595-$C5D0` | 60 | (unnamed) | **Reached code, mis-filed as data.** The tail of `MainLoopBody` at `$C573` and the sound-init call at `$C595` | reached from `MainLoopBody` at `$C573` | | `$C5F8-$CF00` | 2313 | `InitialSpriteDataTable` (`$C844-$C84F`), `UnreachedTileList` (`$C6AC-$C6FF`), otherwise unreached code | `$C5F8-$C6AB` = unreached code (the bytes decode as instructions such as `bpl`/`pla`/`bit`). `$C6AC-$C6FF` = 84 CHR tile indices in rows of 16 - the gunmen's target graphics; the ROM does not connect them to a reader in this dump. `$C700-$C74B` = unreached code plus a 14-entry pointer list at `$C73C`. `$C74C-$C843` = the sound driver's note and instrument data. `$C844-$C84F` = the 12 bytes of the initial OAM table (3 sprite entries of Y, tile, attribute, X) | `lda $C844,x` at `$C056` (X = 0-11) copies the 12-byte OAM table to the sprite buffer - the reset path calls `InitSpriteDataFromTable` at `$C054`. No instruction in this dump reads `$C6AC` | | `$CF0D-$D01B` | 271 | (unnamed) | **Reached code, mis-filed as data.** `MainLoop_Sub1` at `$D00F`, `MainLoop_Sub2` at `$D01C` and the game logic between them | reached from `MainLoopBody` at `$C573` | | `$D417-$D4F3` | 221 | (unnamed) | **Reached code, mis-filed as data.** The per-frame game logic (`MainLoop_Sub13` at `$D400` and the routines that follow). The analyzer reports `dec $D417` at `$CA73` and `$CA90` as reads of this block, but a `dec` on a ROM address is not a table read - it is a false decode inside the unreached code at `$C9AC`-`$CAFF` | reached from `MainLoop_Sub13` at `$D400` | | `$D504-$D5AE` | 171 | (unnamed) | **Reached code, mis-filed as data.** `MainLoop_Sub17` at `$D504` and the following helpers | reached from `MainLoopBody` at `$C573` | | `$D5D9-$D8D6` | 766 | (unnamed) | **Reached code, mis-filed as data.** `MainLoop_Sub24` at `$D5D9` through the event/lane logic | reached from `MainLoopBody` at `$C573` | | `$D8F6-$D8FE` | 9 | (unnamed) | **Reached code, mis-filed as data.** The `lda $2D / bne $D8FE / lda $3C / sta $33` fragment of the lane update. The analyzer reports `cmp $D8F6` at `$C11D` as a read; that operand is a comparison against an immediate value the code loads, not a table access | reached from `MainLoop_Sub46` at `$D8FF` | | `$D908-$D954` | 77 | (unnamed) | **Reached code, mis-filed as data.** `MainLoop_Sub47` at `$D908` and the scoreboard update that follows | reached from `MainLoopBody` at `$C573` | | `$D99B-$DAD1` | 311 | (unnamed) | **Reached code, mis-filed as data.** The target-hit and scoring routines (`MainLoop_Sub67` at `$D9E9` onwards) | reached from `MainLoopBody` at `$C573` | | `$DAED-$DED3` | 999 | (unnamed) | **Reached code, mis-filed as data.** `MainLoop_Sub71` at `$DAED` through the round-end and mode-change logic | reached from `MainLoopBody` at `$C573` | | `$DEDD-$DEEC` | 16 | (unnamed) | **Unreferenced data.** Sixteen bytes inside the unreached code at `$DEED`; the last four (`$02 $04 $02 $01`) look like a small parameter list, but the ROM does not settle it | no instruction in this dump references it | | `$DEED-$F371` | 5253 | (unnamed) | **Unreached code.** The whole sound engine's body: the bytes decode as instructions throughout (for example `sty $F0C8` at `$C116` and `cpx $E01F` at `$FD2B`, which the analyzer reports as reads of this block but are code operands). The `$FFF4` and `$FFF7` jump stubs at the end are the `jmp $F372` and `jmp $F40F` vectors | no instruction in this dump references it | | `$F3DB-$F3E2` | 8 | (unnamed) | **Unreferenced data.** Eight bytes (`80 7F 38 90 80 7F 91 38`) at the head of the unreached sound code; the repeated `$80 $7F` pair looks like an APU period value, but the ROM does not settle it | no instruction in this dump references it | | `$F3EF-$F40E` | 32 | `NoteVolumeTable` (`$F3EF-$F3F9`) | 11 bytes (`95 85 40 D0 3F 7F 0F 08 16 14 12`) of note/volume constants at the head of the unreached sound code; the rest of the block is code (`bpl $F40A` at `$F3FA`) | no instruction in this dump reads it - the sound routine that would consume it is itself unreached code, so the reading rests on the values alone | | `$F423-$F425` | 3 | (unnamed) | **Unreferenced data.** Three bytes (`7F 0F 08`) at the head of `SoundSub7` | no instruction in this dump references it | | `$F48F-$F4EE` | 96 | `VolumeEnvelopeTable` ($F48F-$F4AF) | 39 bytes of volume-envelope steps - `$0E $0B` then a run of `$05`, then `$04`, `$03`, `$02`, `$01` - the volume ramp each instrument fades down through. The rest of the block is code | no instruction in this dump reads it - the sound routine that would consume it is itself unreached code, so the reading rests on the values alone | | `$F77C-$FFF3` | 2168 | (unnamed) | **Unreached code.** The rest of the sound engine: the bytes decode as instructions throughout (`asl a`, `ora ($xx),y`, `sta $2006`, ...). This block was previously labelled `SpritePositionTable`, which was wrong - it is code, not a table | no instruction in this dump references it | | `$FFFA-$FFFF` | 6 | (unnamed) | The three interrupt vectors: NMI = `$C086`, RESET = `$C000`, IRQ = `$C000` | no instruction in this dump references it; the vectors are read by the CPU | ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. --- ## Entry-point naming Every `jsr`/`jmp` target in this project now carries a semantic name; **none** is left in the address-derived `Sub_CXXX` form. The three that were still address-derived are named here: | Address | Name | What it does | |---------|------|--------------| | `$C3A3` | `RunPpuWriteList` | Walks the PPU command list at `($00),y`: a non-zero byte is a command the PPU writer at `PpuWriteAddrAndData` carries out, a zero byte ends the list | | `$C5DF` | `HideScreenForNmi` | Waits for the NMI phase, then clears bits 3 and 4 of the PPUMASK shadow (`Zp09`) and writes it to PPUMASK, so the background and the sprites are off while the NMI handler works | | `$C5EC` | `ShowScreenAfterNmi` | The other half of the pair: waits for the NMI phase, sets bits 3 and 4 of `Zp09` and writes the result back to PPUMASK |