- Wild Gunman (U): Updated OVERVIEW.md to Pass 2/3, added SpriteOamTable comment at , fixed build path references - Joypad Test Cartridge (U): Updated OVERVIEW.md to Pass 2/3, fixed build path references - Donkey Kong Jr. (JU): Fixed root OVERVIEW.md summary table (was Pass 1, actually Pass 2/3 per project OVERVIEW.md) - Root OVERVIEW.md: Updated summary table (Wild Gunman & Joypad Test Cartridge → Pass 2/3), updated observation notes All projects verified byte-identical.
159 lines
6.5 KiB
Markdown
159 lines
6.5 KiB
Markdown
# 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.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 `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 | Complete | Routine headers added for all labeled positions; key routines semantically named (`Reset`, `NmiHandler`, `MainStateDispatch`, `MainLoopBody`, etc.); zero-page aliases in `locals.inc` |
|
||
| Pass 3 | Complete | Data tables/structures identified and named (`SpriteOamTable` at $C844, `VramWriteParameterTable` at $C09E); remaining `Label_Cxxxx` references are internal branch targets within routines |
|
||
|
||
---
|
||
|
||
## 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 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 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 sprite table at $C844 into its own labeled `.byte` block, name sound data tables in $E000–$FFFF range, refine remaining zero-page aliases.
|