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

14 KiB
Raw Blame History

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

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.