Files
disasm/Balloon Fight (JU)/OVERVIEW.md
T

265 lines
13 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.
# Balloon Fight (JU) — Game Flow from Power-On
This document describes the execution flow of the disassembled Balloon Fight (JU) 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. Reset Vector — CPU $FFFC → $C000
**`Reset`** ($C000–$C07E)
On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization:
| Step | Code | Purpose |
|------|------|---------|
| 1 | `lda #$00 / sta PPUCTRL` | Disable NMI and PPU rendering |
| 2 | `lda #$00 / sta PPUMASK` | Disable screen output |
| 3–6 | Poll `$2002` twice | Wait for two full VBlank periods (PPU warm-up) |
| 7 | `sei / cld` | Disable interrupts, clear decimal mode |
| 8 | `ldx #$FF / txs` | Initialize stack pointer |
| 9–13 | Clear Zero Page | Zero all of RAM $00–$FF |
| 14–21 | Warm-start signature check | Compare RAM `$07FA`–`$07FC` against `"HAL"` ($C082–$C084). If valid, skip full memory clear |
| 22–31 | Initialize memory | Clear zero page + stack page ($0700–$07FF), set `$15 = $32`, run block-init loop (`jsr $D6DE` 50 times), `jsr $C579` |
| 32–44 | Copy initialization data | Copy 15 bytes from $C085 to $0629 (initial game state table) |
| 45–51 | Clear local variables | Zero $03–$06 |
| 52–56 | Copy saved signature | Re-store `"HAL"` to $07FA–$07FC |
| 57 | `jmp MainInitialization` | Transfer control to $F1D4 |
**Warm-start signature:** `"HAL"` stored at $C082; RAM copy at $07FA. This lets the game resume state after a soft reset without re-initializing everything.
---
## 2. Main Initialization
**`MainInitialization`** is at **$F1D4** (forward-referenced in `locals.inc`). It sets up the first round, initializes object pools, and enters the main loop. Its body lives in the upper bank region (not fully shown in the decoded $C000–$C07E range).
---
## 3. Interrupt Vectors — CPU $FFFA–$FFFF
Located at the end of the bank (PRG offset $3FFA):
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C094 | `NmiHandler` |
| RESET | $C000 | `Reset` |
| IRQ | $C0F7 | `IrqHandler` (self-loop `jmp $C0F7` — unused) |
---
## 4. NMI / Frame Heartbeat — `NmiHandler` ($C094)
The NMI fires once per frame and drives the frame pacing. It:
1. **Preserves registers** (A, X, Y on stack)
2. **OAM DMA**: writes `$00` to `$2003`, then `$02` to `$4014` (DMA from $0200–$02FF)
3. **PPU command buffer flush**: compares read/write offsets at $52/$53; if data pending, calls `FlushPpuCommandPreserved` ($C17C)
4. **Transfer sources**: `jsr UpdatePpuTransferSources` ($D60D)
5. **Build OAM**: `jsr BuildFrameOam` ($D798)
6. **Frame counter**: `inc FrameCounter` ($19)
7. **Scroll/rendering setup**: sets $2006/$2005 scroll registers, conditionally waits for VBlank if rendering disabled, writes $2000
8. **Audio**: `jsr UpdateAudioFrame` ($FFF7 bank call → `Sound_F758`)
9. **Sets `NmiCompletedFlag`** ($02 = $01)
10. **Restores registers, `rti`**
The main game loop runs during VBlank and waits for this flag.
---
## 5. Main Game Loop — $C209
After initialization, control reaches the per-frame loop at **$C209**:
```
UpdateGameplayObjects ($E691) ; update all 20 gameplay-object slots
UpdateSpecialObjectSystem ($C6F9) ; every other frame (frame counter / 2)
[round spawner / HUD / spawning logic]
[object physics & collision]
[particle system]
[OAM sorting / sprite setup]
WaitForFrame ($F465) ; wait for NMI-completed flag
jmp back to $C209
```
Key flow details:
- **Scroll handling** ($C227): if `PpuScrollX` ($17) is non-zero, toggles `PpuControlBase` bit 0 (screen scroll page flip), decrements scroll X.
- **Carrier/rescue sequence** ($C233): updates carrier position ($0488), target player, sound requests.
- **Effect object pool** (8 slots, $055D–$0584): updates timers, respawns offscreen.
- **Gameplay object pool** (20 slots, $0530–$055C): movement, position integration.
- **Round spawner** ($C790): on frame boundaries, counts down `RoundSpawnDelay`, finds free slots, spawns round objects from tables ($C88F–$C8B5), plays spawn sound.
- **HUD updates** ($C831): when spawn delay reaches 1, writes round HUD digits to PPU buffer.
- **Sprite sorting**: `SortOrInitializeRecords` ($C579) maintains a sorted table of up to 50 five-byte records ($0700–$07F9) for OAM priority.
---
## 6. Subsystems
### 6.1 PPU Command Buffer ($C12D–$C1C4)
A buffered PPU update system. Commands are staged at $0300 with format:
- Byte 0: PPU address high | $20
- Byte 1: PPU address low
- Byte 2: length (number of data bytes following)
Routines:
- `AppendBytesToPpuBuffer` ($C131) — appends raw bytes
- `AppendLengthPrefixedPpuData` ($C139) — appends with length prefix
- `BuildPpuCommand` ($C154, formerly `Sub_C154`) — formats address+length command
- `FlushPpuCommandPreserved` ($C17C, formerly `Sub_C17C`) — drains buffer to $2006/$2007, preserving X/Y
- Special handling for palette writes ($3Fxx range)
### 6.2 Round Spawning ($C77A–$C88A)
- `ChooseRoundSpawnIndex` ($C77A): picks a random spawn index below `RoundSpawnRandomLimit`, rejecting zero
- `UpdateRoundSpawner` ($C790): timer-driven spawning; initializes object position, velocity (from tables at $C89F–$C8B1), flip flags, aux state; increments `SpawnSelector`; requests spawn sound ($F0 bit $04)
- `UpdateRoundHud` / `ConfigureRoundPresentation` ($C831–$C88A): writes round number / HUD tiles to PPU buffer
**Spawn tables** ($C88F–$C8B5): flip X/Y, X/Y offsets, parameter, velocity Y, position hi, velocity X — six entry types.
### 6.3 Gameplay Objects ($C8B7–$C9DC)
`UpdateRoundObjectSpritesAndPhysics` ($C8B7) updates slots 1 and 0:
- Builds 3-sprite OAM per object from tile tables ($C9DD–$CA33)
- Integrates fixed-point position ($C9B6)
- Bounds testing (`TestGameplayObjectAgainstBounds`, $CA67) against rectangular regions
- Velocity reflection on collision (`BounceGameplayObjectX/Y`, $CA4F/$CA37)
- Object removal when offscreen
### 6.4 Bonus Objects ($CB74–$CCCA)
- `UpdateBonusObjectSystem` ($CB74): per-frame phase/timer advance for the single active bonus slot
- `TestBonusObjectPlayerOverlap` ($CBB2): rectangle overlap vs both players; applies horizontal/vertical impulses, collision sound
- `TestBonusObjectCollection` ($CC3A): phase-3 collection; starts bonus timer
- `TriggerBonusObjectEffect` ($CCBF): increments effect counter, calls `ApplyBonusEffect` ($E983)
- `DrawBonusObjectHud` ($CCCB): 3-row HUD tile display from phase tables ($CD26–$D046)
### 6.5 Carrier / Rescue Sequence ($C5C3–$C715)
- `UpdateCarrierSequence` ($C6F9): advances carrier target X while idle; finds eligible player; starts sequence
- `FindEligiblePlayerForCarrier` ($C614): scans player slots 7→0, finds one at lower screen boundary ($B4+) matching carrier target X
- `BeginCarrierTargetSequence` ($C6C4): captures player position/state, resets timer
- `UpdateCarrierArrivalSequence` ($C658): moves carrier down, checks contact, transitions player to hit/rescue state, sound request
- `UpdateCarrierObjectDisplay` ($C5C3): builds 8-sprite carrier metasprite, updates target player position relative to carrier
- Tables: `NormalDisplayModeTable` ($C5B7), `AlternateDisplayModeTable` ($C5BB), `CarrierAnimationSequenceTable` ($C6B8)
### 6.6 Particle System ($CD4A–$CEAD)
- `InitializeParticleSlots` ($CD4A): 10 slots, offscreen Y ($F0)
- `SpawnTimedParticle` ($CD5A): random timer, finds free slot, assigns position/velocity from `ParticleInitialVelocityTable` ($CEAE)
- `UpdateParticleSystem` ($CDAA): integrates signed motion, applies scroll delta, retires offscreen particles
- `DrawParticleSprites` ($CE2F): 3-sprite OAM per particle with animation/attribute masking
### 6.7 Sound Driver ($FFF7 region, bank-switched)
- `UpdateAudioFrame` jumps to `Sound_F758` in another bank
- Sound sequence pointer table at $3BCA (32 entries)
- Encoded sequence data $3BCA–$3FF1 (note/duration bytes with $02/$FF/$8x control codes)
- APU channel init at $F7xx region (`InitializeApuChannelState`, `SilencePulseChannels`)
- Sound request flags: $F0 (flags 0), $F1 (flags 1), $F2 (flags 2), $F3 (flags 3)
---
## 7. Memory Map (Key Zero Page / RAM)
| Address | Name | Use |
|---------|------|-----|
| $00 | `PpuControlShadow` | $2000 mirror |
| $01 | `PpuMaskShadow` | $2001 mirror |
| $02 | `NmiCompletedFlag` | VBlank sync flag |
| $12–$13 | `RecordIndex` / `ReverseRecordIndex` | Sorted-record insertion |
| $14–$15 | `SpawnParameter` / `SpawnPixelPosition` | Spawn args |
| $16 | `RenderingDisabledFlag` | Rendering state |
| $17 | `PpuScrollX` | Scroll X |
| $18 | `PpuControlBase` | Base PPU control |
| $19 | `FrameCounter` | Frame tick |
| $1B | `RandomState` | PRNG state |
| $1D–$1E | `RecordPtrLo/Hi` | Record table pointer |
| $23–$24 | `SpawnCommandPointerLo/Hi` | Command stream pointer |
| $25–$26 | `DispatchPointerLo/Hi` | Spawn dispatch indirect jump |
| $52–$53 | Ppu buffer read/write offsets | Command buffer |
| $54–$56 | Ppu command address/length | Pending PPU command |
| $7F | `PlayerLifecycleState` | Per-player lifecycle |
| $88 | `PlayerSlotState` | Per-player active state |
| $91 | `PlayerPosX` / `Player0PosX` | Player X |
| $99 | `CarrierTargetX` | Carrier target |
| $9A | `PlayerPosY` / `Player0PosY` | Player Y |
| $A2 | `CarrierPosY` | Carrier Y |
| $BD | `PlayerCollisionDisable` | Collision immunity |
| $F0–$F3 | Sound request flags 0–3 | Audio requests |
| $0300–$03FF | PPU command buffer | Staged PPU updates |
| $0489–$048D | Carrier sequence state | Active, mode, target, drop, timer |
| $0490–$0530 | Gameplay object pool (20 slots) | State, pos, velocity |
| $055D–$0584 | Effect object pool (8 slots) | State, pos |
| $0558–$05CE | Particle / sequence state | 10-slot particle pool |
| $05D1–$060E | Bonus object state | Timer, phase, pos, HUD |
| $0700–$07F9 | Sorted record table | 50 × 5-byte OAM sort records |
| $07FA–$07FC | Warm-start signature | "HAL" |
---
## 8. Object Pools
| Pool | Slots | State array | Purpose |
|------|-------|-------------|---------|
| Gameplay objects | 20 ($00–$13) | $0530 | Round enemies/objects, movement & physics |
| Effect objects | 8 ($00–$07) | $055D | Short-lived spawn effects |
| Particles | 10 ($00–$09) | $055D | Visual particles (shared state array, different offset interpretation) |
Slot state convention: **negative ($FF) = inactive/free**, zero or positive = active.
---
## 9. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C000) ────► hardware init, clear RAM, validate "HAL" signature
│
▼
jmp MainInitialization ($F1D4)
│
▼
[first-round setup, object pool init]
│
▼
Main Loop ($C209) ──────────────────────────────────────────────┐
│ │
├─ UpdateGameplayObjects ($E691) │
├─ UpdateSpecialObjectSystem ($C6F9) [alt frames] │
├─ Carrier sequence ($C6F9 family) │
├─ Round spawner ($C790) + HUD ($C831) │
├─ Object physics & collision ($C8B7, $CA67) │
├─ Bonus object system ($CB74) │
├─ Particle system ($CDAA) │
├─ OAM sort ($C579) │
├─ WaitForFrame ($F465) ◄──────── waits for NMI flag ──────┘
│
▼ (loop back to $C209)
NMI ($C094) [once per frame]
├─ OAM DMA ($4014)
├─ Flush PPU command buffer ($C17C)
├─ Update transfer sources ($D60D)
├─ Build frame OAM ($D798)
├─ Frame counter++, scroll setup
├─ UpdateAudioFrame ($FFF7 → Sound_F758)
└─ Set NmiCompletedFlag, rti
```
---
## 10. Notes on Provisional Areas
Some routines in the disassembly still carry placeholder names (`Label_Cxxxx`, `Sound_FBxx`) and some data blocks were previously marked "undecoded." These have been cleaned up in this review:
- `Sub_C154` → **`BuildPpuCommand`**
- `Sub_C17C` → **`FlushPpuCommandPreserved`**
- `Sub_C40C` → **`SpawnRandomObject`**
- `SpawnCommandStreamData_C4AD` → **`RoundSpawnCommandStream`** (format documented)
The 497 `Label_` branch targets are local intra-routine labels; none are subroutine entry points (confirmed against all `jsr`/`jmp` targets in the source). The 77 `Sound_FBxx` labels are sound-driver entry points in the banked audio region.
Build verification: `make` in `copilot/` produces `reconstructed_prg.bin` that is **byte-identical** to `prg.bin` and to the PRG bank of `balloon.nes`.