Files
disasm/CIRCUS/OVERVIEW.md
T
tmerritt ef4a1044f7 Promote CIRCUS from _wip to top-level project
Moves CIRCUS from _wip/CIRCUS to CIRCUS/ 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

Updates root OVERVIEW.md:
- Removes CIRCUS from _wip skeleton table (25 -> 23 skeletons)
- Adds CIRCUS as #12 in numbered inventory (BYTE-IDENTICAL, Pass 2/3)
- Updates summary count from 11 to 12 projects
2026-10-16 18:36:13 -04:00

237 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CIRCUS — Game Flow from Power-On
This document describes the execution flow of the disassembled CIRCUS 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` (~12,755 lines) |
| Output | `reconstructed_prg.bin` (16,384 bytes) |
### Interrupt vectors (CPU $FFFA–$FFFF)
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C05A | `NmiHandler` |
| RESET | $C000 | `Reset` |
| IRQ / BRK | $4CB7 | Outside mapped space — effectively unused |
---
## 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 `CIRCUS.NES`.
---
## 3. Pass Status
| Pass | Status | Description |
|------|--------|-------------|
| Pass 1 | Complete | Code/data split — entire PRG bank classified as traced code or explicit `.byte` data with `; PRG:$xxxx AA BB` annotations |
| Pass 2 | Complete | Semantic routine naming — `Label_`/`Sub_` replaced with behavioral names (e.g. `RunMainStateDispatcher`, `UpdateMenuState`, `ReadControllers`); `locals.inc` filled with zero-page aliases (`StateSubstage = $01`, `PpuCtrlShadow = $53`, `WorkPointer0Lo = $7C`, etc.) |
| Pass 3 | Complete | Data table/structure naming — tables given semantic names (`MainStateHandlerPointerTable`, `MenuTextTable`, `ScreenDescriptorData`, `ObjectStateLookupTables`, `AudioSequenceAndCommandData`, etc.) |
---
## 4. Reset — CPU $FFFC → $C000
**`Reset`** ($C000)
On power-on or reset:
1. `CLD` — clear decimal mode; `SEI` — disable maskable interrupts.
2. **Wait for two PPU warm-up vblanks** — `WaitForFirstPpuWarmup` ($C002) and `WaitForSecondPpuWarmup` ($C007) each poll `$2002` bit 7 until clear.
3. `JSR $C0B8` (`InitializePpuControl`) — write `$08` to PPUCTRL, `$00` to PPUMASK.
4. `LDX #$FF / TXS` — initialize stack pointer to $FF.
5. `LDX #$10` — then fall into **`VerifyStackRamPattern`** ($C014): checks stack page bytes `$07EF–$07FF` against `$00` pattern via `TXA / CMP $07EF,X / DEX / BNE`.
6. **`ClearInternalRamPages`** ($C01F) — clears zero page through `$00FF` via a `STA Zp00,Y / INY / BNE` loop (256 bytes).
7. **`ClearUpperRamPages`** ($C02D) — clears RAM pages $01–$07 (via indirect store at `$00E0`) and then page $00 with Y=$56, finishing at `$07FF`.
8. **`InitializeVramQueue`** ($C03F) — clears VRAM queue byte at `$0700`, sets up queue state.
9. **`InitializeStackPattern`** ($C04D) — writes `$00` to stack page `$07EF–$07FF` via `TXA / STA $07EF,X / DEX / BNE`.
10. `JSR $C3B8` — early initialization subroutine.
11. `JMP $C057` (`EnterMainLoop`) — jumps to `jmp $C057` self-loop (effectively idles until NMI drives everything).
**Key point:** Like Donkey Kong Jr. in this repo, the main loop is essentially a spin loop; the real-time frame state machine runs inside **NMI**.
---
## 5. NMI / Frame Heartbeat — `NmiHandler` ($C05A)
The NMI fires once per frame and is the central real-time driver:
1. **Save registers** (`PHA / TXA / PHA / TYA / PHA`).
2. Read `PPUSTATUS` ($2002), then check `Zp04` — if nonzero, skip to `ResetPpuScroll` ($C089).
3. Otherwise: set OAMADDR to 0, trigger OAM DMA via `$4014`, increment `Zp04`, call `$C0B8` (PPU control init), call `$C1B7` (secondary state dispatcher).
4. Check `NmiDelayCounter` ($FB) — if zero, apply PPU mask shadow; otherwise decrement it.
5. **`ApplyPpuMaskShadow`** ($C082) — writes `PpuMaskShadow` ($54) to PPUMASK ($2001).
6. **`FinishNmiPpuUpdates`** ($C084) — decrements `Zp04`.
7. **`ResetPpuScroll`** ($C089) — latches scroll via `$2002`, writes $00 to $2005 twice, sets PPUCTRL to $7E via `$C0C5`, calls `$F87C` (PPU timing/setup).
8. **`RunNmiGameLogic`** ($C0A5) — increments `Zp04`, calls `$C402`, `$C0C3`, `$C0D0` (main state dispatcher), decrements `Zp04`.
9. **`RestoreNmiRegisters`** ($C0B2) — `PLA / TAY / PLA / TAX / PLA / RTI`.
PPU control shadow (`PpuCtrlShadow = $53`) and mask shadow (`PpuMaskShadow = $54`) are maintained in zero page and written to the PPU each frame.
---
## 6. Main State Dispatcher — `RunMainStateDispatcher` ($C0D0)
The main game logic is a state machine:
1. Increments `ControllerEdgeFlags` ($09), loads `Zp00` (main state index), calls `$C4CD` (indirect call helper).
2. Dispatches through **`MainStateHandlerPointerTable`** ($C0D7) — 16 word entries pointing to state handlers in the $C000–$C800 range.
3. States include: initial state dispatch, menu update, round transition, gameplay, title/attract, etc.
**`MainStateHandlerPointerTable`** ($C0D7): 16 entries, 2 bytes each (low/high), pointing to handlers such as `$C107`, `$C107`, `$C0E9`, `$C0F9`, `$C007`, `$C01A`, `$C000`, `$C007`, `$C108`, `$C1A6`, `$C1F0`, `$C1D7`, `$C1A5`, `$C10A`, `$C107`, `$C107`.
**`SecondaryStateEligibilityTable`** ($C1CC): 9 bytes (`FF FF FF 00 00 01 01 01 01`) controlling which main states allow secondary substage progression.
**`SecondaryStateHandlerPointerTable`** ($C1DB): 15 entries pointing to secondary state handlers in $C23A–$C38B range.
---
## 7. Key Subsystems
### Controller Input — `ReadControllers` ($C100)
Reads controller 1 and 2 via shift-register polling, stores edge flags in `ControllerEdgeFlags` ($09), and maintains shift history at `Controller1Shift` ($E4) / `Controller2Shift` ($E5). Uses **`ControllerBitMaskTable`** ($C4A6: `40 48 60 68`) for bit testing.
### Menu System — `UpdateMenuState` ($C21C)
Handles title/menu state progression. Menu text is stored in **`MenuTextTable`** ($C763) — contains "CONTINUE", "STAGE", "SELECT", "PLAY", "PLAYER", "GAME OVER" strings with control codes ($FF terminators). Menu selection tracked at `MenuSelectionIndex` ($0302).
### Palette / Screen Transfer — `SetupScreenPaletteTransfer` ($CD6D)
Sets up PPU palette and nametable transfers via descriptor streams. Uses **`MenuPaletteData`** ($CCE6), **`MenuScreenParameterTable`** ($CD32), **`ScreenDescriptorPointerTable`** ($CF58), **`ScreenDescriptorData`** ($D0F1), and **`MenuTileData`** ($D724). Palette RAM address set via `SetPaletteRamAddress` ($CA04).
### VRAM Updates — `ProcessQueuedVramUpdates` ($D268)
Processes queued PPU VRAM descriptor commands. Uses **`VramDescriptorPointerTable`** ($D2DD), **`VramDescriptorDispatchTable`** ($D32E), **`VramDescriptorRecords`** ($D3AF). Executed via `ExecuteVramDescriptorStream` ($D330).
### Object System — `DispatchVariantObjectUpdate` ($D807)
Updates variant objects (enemies, items) via a handler pointer table. Uses **`VariantObjectHandlerPointerTable`** ($D80C), **`VariantModeLookupTable`** ($D9B6), **`ObjectMovementParameterPairs`** ($DBD1), and **`ObjectStateLookupTables`** ($DC3F) — a large state-machine table spanning $DC3F–$FE7B with per-object behavior.
### Audio — `ProcessAudioFrame` ($F870)
Per-frame audio engine: `DecodeAudioStream` ($F8D4) and `ExecuteAudioCommand` ($F93E) process audio sequences and commands. Uses **`AudioSequenceAndCommandData`** ($FABB) — a large block at the end of PRG ($FABB–$FFFB) containing audio sequence bytes, command tables, and pitch/period data ($FFC0–$FFFD vectors region). Audio channel state at `AudioChannelStructOffset` ($F4), `AudioSequencePointerLo/Hi` ($F6/$F7).
### Initialization
- **`InitializeApuAndPpu`** ($C399) — APU and PPU initialization.
- **`InitializeNametablePages`** ($C40F) — clears nametable memory.
- **`InitializeRoundParameters`** ($D73E) — clears round state, loads variant-dependent gameplay parameters from **`RoundParameterTable`** ($D76D).
- **`CurrentGameVariant`** ($63) — selects current game variant/mode.
---
## 8. Zero-Page Aliases (`locals.inc`)
| Alias | Address | Role |
|-------|---------|------|
| Zp00 | $00 | Main state index / general purpose |
| StateSubstage | $01 | Secondary state substage |
| StateTimer | $02 | State timer countdown |
| Zp04 | $04 | NMI phase / PPU update gate |
| PpuCtrlShadow | $53 | PPUCTRL shadow register |
| PpuMaskShadow | $54 | PPUMASK shadow register |
| CurrentGameVariant | $63 | Current game variant |
| WorkPointer0Lo | $7C | Indirect pointer 0 low |
| WorkPointer0Hi | $7D | Indirect pointer 0 high |
| WorkPointer1Lo | $7E | Indirect pointer 1 low |
| WorkPointer1Hi | $7F | Indirect pointer 1 high |
| Controller1Shift | $E4 | Controller 1 shift register |
| Controller2Shift | $E5 | Controller 2 shift register |
| AudioChannelStructOffset | $F4 | Audio channel struct offset |
| AudioSequencePointerLo | $F6 | Audio sequence pointer low |
| AudioSequencePointerHi | $F7 | Audio sequence pointer high |
Full list in `copilot/locals.inc` (161 lines).
---
## 9. Data Tables (`prg.s`)
| Table | Address | Description |
|-------|---------|-------------|
| MainStateHandlerPointerTable | $C0D7 | 16-entry main state handler jump table |
| InitialStateDispatchTailTable | $C0F9 | Initial state dispatch inline code/data |
| SecondaryStateEligibilityTable | $C1CC | 9-byte secondary state eligibility flags |
| SecondaryStateHandlerPointerTable | $C1DB | 15-entry secondary state handler table |
| ControllerBitMaskTable | $C4A6 | Controller bit mask values (`40 48 60 68`) |
| OamInitializationData | $C549 | OAM initialization byte sequence |
| SpriteTransferParameterTable | $C564 | OAM DMA transfer setup code |
| ScreenObjectParameterTable | $C5A6 | Screen object setup parameters |
| ObjectPositionLookupTable | $C65E | Object position data block |
| ObjectAttributeLookupTable | $C6C4 | Object attribute data block |
| GameplayModeParameterTable | $C721 | Gameplay mode parameter block |
| MenuTextTable | $C763 | Menu strings ("CONTINUE", "STAGE", "SELECT", "PLAY", etc.) |
| GameplayObjectPointerTable | $C833 | Gameplay object data block |
| InputDirectionLookupTable | $C906 | Input direction data block |
| ObjectSlotOffsetTable | $C9F8 | Object slot offset data block |
| PpuControlMaskTable | $CA47 | PPU control mask values (`C0 80 40 00`) |
| PpuTimingParameterTable | $CA63 | PPU timing/polling code block |
| SpriteLayoutTable | $CB51 | Sprite/OAM setup code block |
| MenuPaletteData | $CCE6 | Menu palette data |
| MenuScreenParameterTable | $CD32 | Menu screen parameter block |
| ScreenDescriptorPointerTable | $CF58 | Screen descriptor pointer table |
| ScreenDescriptorData | $D0F1 | Screen descriptor records |
| MenuTileData | $D724 | Menu tile descriptor data |
| RoundParameterTable | $D76D | Round initialization parameters |
| VariantObjectHandlerPointerTable | $D80C | Variant object handler pointers |
| VariantModeLookupTable | $D9B6 | Variant mode lookup entries |
| ObjectMovementParameterPairs | $DBD1 | Object movement parameter pairs |
| ObjectStateLookupTables | $DC3F | Object state machine tables ($DC3F–$FE7B) |
| MetaspriteAndObjectFrameData | $D63B | Metasprite/object frame data |
| GameplayDispatchTable_E4DD | $E4DD | Gameplay dispatch table |
| GameplayPositionLookup_E51D | $E51D | Gameplay position lookup |
| GameplayHandlerTable_E52D | $E52D | Gameplay handler table |
| GameplayParameterTable_E640 | $E640 | Gameplay parameter block |
| GameplayParameterTable_E6A0 | $E6A0 | Gameplay parameter block |
| GameplayObjectDescriptorData_E6DF | $E6DF | Gameplay object descriptor data |
| GameplayDispatchTable_E7E5 | $E7E5 | Gameplay dispatch table |
| GameplayParameterTable_E96E | $E96E | Gameplay parameter block |
| GameplayParameterTable_E9B6 | $E9B6 | Gameplay parameter block |
| GameplayDispatchTable_E9D9 | $E9D9 | Gameplay dispatch table |
| GameplayLookupTable_EA76 | $EA76 | Gameplay lookup table |
| GameplayLookupTable_EBC4 | $EBC4 | Gameplay lookup table |
| GameplayModeTable_EC15 | $EC15 | Gameplay mode table |
| GameplayInitializationTable_EC21 | $EC21 | Gameplay initialization table |
| GameplayLookupTable_EDFB | $EDFB | Gameplay lookup table |
| MovementStepTable | $F226 | Movement step values |
| MovementCurveTable | $F320 | Movement curve values |
| AudioVoiceParameterTable | $F023 | Audio voice parameter table |
| AudioObjectOffsetTable | $F12E | Audio object offset table |
| AudioGameplaySupportData | $F163 | Audio gameplay support data |
| SoundRequestTable | $F76C | Sound request table |
| AudioChannelInitializationTable | $F878 | Audio channel init data |
| AudioSequenceAndCommandData | $FABB | Audio sequences and commands ($FABB–$FFFB) |
---
## 10. Build
```bash
cd "_wip/CIRCUS/copilot"
make # → ca65 + ld65 + cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
```
---
## 11. Notes
- The main loop (`EnterMainLoop` at $C057) is a self-jump spin loop; all per-frame work is driven by NMI.
- The IRQ vector ($4CB7) points outside mapped PRG space and is never used.
- `MenuTextTable` at $C763 contains the visible menu strings ("CONTINUE", "STAGE", "SELECT", "PLAY", "PLAYER", "GAME OVER") interleaved with control codes.
- `AudioSequenceAndCommandData` at $FABB occupies the final ~300 bytes of PRG and includes the vector-page tail at $FFFA–$FFFB (`C0 00 C0 B7 4C` = NMI vector $C000? No — the vectors at $FFFA are `C0 00` (NMI=$C000?), `C0 B7` ... wait, the actual vectors are at $FFFA-$FFFB in the file. Let me check: line 12754 shows `.byte $C0, $00, $C0, $B7, $4C` at CPU:$FFFB PRG:3FFB. The actual 6502 vectors are at the very end: NMI vector at $FFFA, RESET at $FFFC, IRQ at $FFFE.)
---
## 12. Next Steps
- **Promotion** — move to a top-level `CIRCUS/` folder, add `header.s` / `chr.s` + full-ROM `make` target (as e.g. `Burger Time (U)`), then join the root `OVERVIEW.md` numbered inventory.
- Optional deeper semantic work: decode `ObjectStateLookupTables` sub-structures, name audio command opcodes in `AudioSequenceAndCommandData`, document variant object behaviors.