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
This commit is contained in:
tmerritt committed 2026-10-16 18:51:15 -04:00
1 parent ef4a1044f7
commit c79a52e354
23 files changed
+5434 -86

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
# 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
```bash
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.