Files
disasm/Baseball (UE)/OVERVIEW.md
T

433 lines
25 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.
# Baseball (UE) — Game Flow from Power-On
This document describes the execution flow of the disassembled Baseball (UE) 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 / NES 2.0 |
| PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) |
| CHR | 1 × 8 KB |
| Mirroring | Horizontal |
| PRG bank start | CPU $C000 (`.org $C000`) |
| Linker output | `reconstructed_prg.bin`, byte-identical to `prg.bin` |
### Interrupt vectors (CPU $FFFA–$FFFF)
| Vector | Address | Handler | Notes |
|--------|---------|---------|-------|
| NMI | $C046 | `NMI` | Runs once per frame; the primary frame driver |
| RESET | $C40B | `Reset` | Cold-start initialization |
| IRQ / BRK | $C43E | `IRQ` | Just `rti` (unused) |
---
## 2. Reset — CPU $FFFC → $C40B
**`Reset`** ($C40B)
On power-on or reset:
1. `sei` / `cld` — disable maskable interrupts, clear decimal mode.
2. **Clear CPU RAM** $0000–$07FF — one 256-byte page at a time using the indirect pointer at $00/$01, repeatedly writing $2000 to PPUCTRL during the clear.
3. `jsr InitializeSpriteSortTables` ($C7B1) — set up the sprite sort/index tables at $0377, $038C, $0399.
4. `jsr DisablePpuRendering` ($E96C) — turn off PPU output.
5. **Disable APU** — write $00 to APUSTATUS ($4015) to silence all channels, then select the five-step frame-counter mode and inhibit frame-counter IRQ via JOY2 ($4017).
6. `jsr ExpandRecordPointerList` ($C8BB) with inline data — builds record pointer structures.
7. `lda #$1A / sta PpuMaskShadow` — initial PPU mask shadow value.
8. `jmp InitializeTitleScreen` ($C74A) — RESET does not return; it transfers directly to the title screen.
---
## 3. NMI / Frame Heartbeat — `NMI` ($C046)
The NMI fires once per frame and is the central frame driver. It does **not** simply return; it performs substantial per-frame work:
1. **Save X and Y** into `NmiSavedX` ($0D) / `NmiSavedY` ($0E).
2. **Read PPUSTATUS** ($2002) to reset the PPU address latch.
3. **OAM DMA** — write $02 to OAMDMA ($4014) to copy $0200–$02FF to sprite memory.
4. **Process the PPU command queue** at $0300 — walk variable-length packets (write packets to $2007, read packets discarded), terminating on a zero-length entry, then reset the queue head (`stx $0300`).
5. **Apply scroll** — write `ScrollX` ($DF) and `ScrollY` ($E0) to $2005.
6. **Apply PPU registers** — write `PpuMaskShadow` ($DE) to $2001 and `PpuCtrlShadow` ($DD) to $2000.
7. `jsr InitializeAudioEngine` ($F9D0) — initialize/advance the audio engine.
8. **Latch controllers** — strobe JOY1 ($4016) with $01 then $00.
9. **Read both controllers** — sample JOY1/JOY2 eight times each, shifting button states into `ControllerPressed1` ($15) / `ControllerPressed2` ($16).
10. **Update controller state** — compute newly-pressed edges (`Pressed = (Pressed & ~Held) | raw`) and update `ControllerHeld1/2` ($11/$12).
11. **START-button branch**:
- If START pressed and `SessionActive` ($1E) is set → `jsr ProcessQueuedObjectEvent` with command $0B.
- If START pressed and session is **not** active → set `TransitionActive` ($1D) and `GameplaySuspended` ($EF), then `jmp InitializeNewSession` ($C49C).
12. `NMI_CheckState` — if `NmiStateFlag` ($1F) is nonzero, skip ahead and restore registers.
13. Otherwise `jsr QueueInlineObjectEvent` ($EB55) with inline command bytes `$05,$21,$C1`, then `jsr ProcessQueuedObjectEvent` ($EB2A) with command $0B.
14. `inc FrameCounter` ($E3).
15. `jsr CalculateControllerAssignments` ($C87E) — derive per-player control state.
16. Store derived input into `InputState1/2` ($13/$14) and `InputAux1/2` ($17/$18).
17. **Restore X, Y, A**, `rti`.
Because the NMI does controller reading and event dispatch, the main loop largely waits on the NMI-driven queued-event system rather than polling hardware directly.
---
## 4. Title Screen — InitializeTitleScreen ($C74A)
Shared entry point used by RESET and by post-match/transition paths:
1. `jsr InitializeQueuedEventSystem` ($EB9D) — clear the $0600–$06FF event page, the queue count ($E2), active-event slots, and set `CurrentObjectIndex` ($E1) = 1.
2. `GameplaySuspended` ($EF) = 0; `TransitionActive` ($1D) = 1.
3. `PpuCtrlShadow` = $80; disable PPU rendering; fill PPU memory (`FillPpuMemoryAfterValueSaved` at $E985 with value $25).
4. `jsr ProcessInlinePpuStream` ($EA63) twice — streams title PPU data (inline records `$8D,$F8` and `$00,$C0`).
5. `jsr EnableNmiAndWaitForFrame` ($E978) — enable NMI and synchronize to a frame.
6. Enable APU channels ($4015 = $0F), set timing byte $FD = $08.
7. `RefreshTitleScreen` ($C77A) loop:
- Update `ModeFlags` ($40).
- `jsr QueueModeIndicatorTiles` ($E5CF).
- Wait for controller input release (SELECT/$20) and for the frame counter / delay byte $1B to elapse.
8. On exit: `TransitionActive` = 0, `GameplaySuspended` = 1, clear audio ($4015), re-init the event system, queue an inline command `$03,$88,$C4`, then `jmp InitializeSessionDisplay` ($C4AA).
### Attract mode — RunAttractMessageLoop ($C43F)
Before reaching the title input loop, `Reset` can flow into `RunAttractMessageLoop`, which:
- Clears `NmiStateFlag`, selects object event bank 1.
- Draws encoded six-character messages via `CopyInlineDisplayRecord` ($EA17) with inline text bytes.
- Exchanges the object event bank ($EB4E), increments `NmiStateFlag`, and loops back to itself — this is the idle attract display until the player presses START.
---
## 5. Session and Match Initialization
### InitializeSessionDisplay ($C4AA)
PPU shadow setup, disable rendering, fill PPU memory with $26, then process an inline PPU stream (`$F2,$F4`).
### InitializeMatchState ($C4B9)
- Clears base position and acceleration fields ($C94F/$C943).
- Sets `$053B` and `PositionSignState` ($B7) to $80.
- `jsr EnableNmiAndWaitForFrame`.
- **Player/team selection**:
- `Random0To5` ($C850) → `PlayerOneSelection` ($34).
- Repeatedly `Random0To5` until a value different from player one → `PlayerTwoSelection` ($35).
- If `GameplaySuspended` is set, skip side selection; otherwise derive `ActivePlayerSide` ($4B) from mode flags or a random bit.
- Queue inline object events `$03,$19,$C8` and `$02,$19,$C8`, set `PlayerSelectionActive` ($44) = 1.
- `WaitForPlayerSelection` ($C4FF) spins until `$43` (`SelectionState`) becomes negative, then queues `$03,$3E,$C4` and falls into `CompleteMatchSetup` ($C50C).
### CompleteMatchSetup ($C50C)
- `FillSelectionBackgroundBlocks` ($C85A), `QueuePlayerSelectionDisplay` ($C7DD), `InitializeRandomBitfields` ($C899), `QueuePlayerValueDisplay` ($CF8B).
- Queue inline command `$09,$37,$CE`, then enter `InitializeRoundState` ($C51E).
---
## 6. Round / Gameplay State Machine
### InitializeRoundState ($C51E)
Round-setup entry point:
- If gameplay is not suspended and `RoundStateIndex` ($33) < $12, select round music cue; otherwise reset round counters.
- `EnableRoundAudio` ($C52A): enable APU channels ($4015 = $0F), set $FD = $08.
- `ResetRoundCounters` ($C536): clear scores ($67/$68) and `RoundStateIndex`.
- `jsr InitializeObjectsFromTable` ($D06B) — initialize objects from the table at $D0A0–$D131.
- Set `$053B` / `PositionSignState` = $80.
- Store a $3C-tick delay, draw an 8-character message, store another $3C delay.
- `SelectRoundMusicCue` ($C566): choose a music delay value ($20 or $40) based on `RoundStateIndex` parity → store in $FD.
- `PrepareRoundObjects` ($C573): queue player-selection display, queue random-state record, clear `MatchCompleteFlag` ($45) and `SecondarySequenceState` ($64).
### ResetGameplayState ($C57F)
- Clear `InningOrSharedScore` ($66), `BaseMotionActive` ($5C).
- Mark four state entries ($0598–$059B) inactive ($FF).
- `jsr QueueRoundNumberDisplay` ($CF67), re-init objects, set sign state to $80.
- Store a $B4-tick delay.
### Round setup continuation points
- `ResumeRoundSetupAtRandomDirection` ($C5A4): queue random-direction record, clear primary/transition sequence state, set `StateSequenceActive` ($46) = 1, clear state field pairs ($03,$23 via $CF51).
- `ResumeRoundSetupAtSelectionDisplay` ($C5BA): clear the 8-character field, clear $0597–$05A6, clear the 38-byte motion field range $056F–$0594.
- Based on `RoundStateIndex` and score comparison ($67 vs $68), either jump to `CompleteMatchState` ($C6F7) or finish the reset.
### FinishGameplayReset ($C5F1)
- `InitializeDefaultObjects` ($D030), clear sign state, select event bank 1 → enter `RunRoundStateLoop` ($C5FE).
### RunRoundStateLoop ($C5FE) — main per-round loop
This is the core gameplay loop:
- If `PendingStateTransition` ($60) is set → `InitializeObjectStateLookup` ($C8A3).
- If either `PendingStateTransition` or `StateSequenceActive` ($46) is nonzero → dispatch state-specific handlers via queued inline commands (`$0D,$77,$D4`, `$0F,$81,$D3`).
- `RunPrimaryRoundHandlers` ($C619):
- `InitializeSecondaryActors` ($D633), `InitializeConditionalObject` ($D126).
- Clear working state: `PendingScoreIncrement`, `BaseMotionPhase`, `BaseMotionEventCount`, `TrajectoryResult`, `PendingStateTransition`, `SpecialSequenceState`, `ObjectTransitionState`, `BaseMotionActive`, `ActionSequenceCounter`, `TrajectoryEvaluationEnabled`, and several others.
- Clear base acceleration and position fields.
- Queue three inline object events (`$25,$5A,$D8`, `$0E,$66,$D1`, `$1F,$76,$CA`).
- `jsr UpdateObjectEventBank_Exchange` with command $08.
- Walk the 22-entry `RoundEventSlotTable` ($C6E1) in reverse, dispatching each via `DispatchNextQueuedEvent` ($EB66).
- Write one inline character ($3F, value $28) to the PPU.
- `ApplyPendingScoreUpdate` ($C67E): if a score increment is pending and no state transition, `jsr UpdateCountersAndDisplay` ($CFA6), clear the increment.
- `StartPostRoundDelay` ($C68D): wait for `PendingEvent` ($6F) to clear, then count down delay counter $48.
- If match is not complete and no sequence is active → `jmp ResumeRoundSetupAtSelectionDisplay` ($C5BA) to start the next round.
- `ProcessPostRoundEvents` ($C6A5): dispatch event slots $0D and $0F, store a $0A-tick delay, decrement a counter based on `RoundStateIndex` parity.
- `CheckPostRoundContinuation` ($C6C5): if match complete → `AdvanceRoundNumber` ($C6CC); otherwise `jmp ResumeRoundSetupAtRandomDirection` ($C5A4).
- `AdvanceRoundNumber` ($C6CC): update counters/display, increment `RoundStateIndex`; if index reaches $12 → `InitializeResultsPosition` ($C708); otherwise `SelectRoundMusicCue` ($C566) to begin the next round.
---
## 7. Per-Frame Object and Motion Updates
### UpdateFrameObjectsAndTimers ($C122)
Called each frame to drive object motion:
- Reset PPU queue offset, OAM write offset, and `FrameState20` ($20).
- Queue/process inline object events ($07,$64,$C2 via bank, then command $01).
- Decrement per-object timers at $063F,X (30 slots); when a timer reaches zero, queue a decoded object event.
- Decrement four short countdowns at $05AD,X.
- If a transition is active, jump to `CommonEngineExit` ($EB6A); otherwise `IntegrateAllObjectPositions` ($C15B).
### IntegrateAllObjectPositions ($C15B)
- For 36 object slots (X stepping by 2, each logical object uses two indices):
- Sign-extend and add horizontal delta ($050B/$050C,X from $056F/$0570,X).
- Sign-extend and add vertical delta ($053C/$053D,X from $0570/$0571,X).
### UpdateBaseMotionSequence ($C18D)
- Integrate velocity/position for three base object indices ($00, $31, $62).
- Reflect base motion values when $056C is negative (negate $056B/$056C and halve $0569/$056A).
- Apply doubled horizontal/vertical velocity adjustments to $0507/$0508 and $0538/$0539.
- `jsr EvaluateTrajectoryResult` ($CAD2).
- Advance `BaseMotionPhase` ($5D); when motion settles, clear base acceleration/position fields.
- `UpdatePositionSignState` ($C228): compute a signed position delta relative to a $0A threshold, track sign changes in `PositionSignState` ($B7), and update rendering flags.
### IntegrateObjectVelocityAndPosition ($C3CA)
Signed fixed-point position integration used by the three base motion call sites: shifts the signed 16-bit velocity right twice (sign-preserving), adds to position ($0507/$0508), then adds the result to the secondary position ($0509/$050A).
---
## 8. Rendering and OAM
### BuildFrameOam ($C2DE)
- If `TransitionActive` is set: copy an 88-byte sprite/OAM template from $F94D into shadow OAM at $0200, set `OamWriteOffset` ($3F), and jump to `TerminatePpuUpdateQueue`.
- Otherwise, if `PlayerSelectionActive` ($44) is set: copy an 8-byte OAM template from $C331, position two sprites from player selection coordinates (scaled ×16 + $50), choose tiles based on `ModeFlags`.
- Otherwise: `SortObjectsByY` ($C339) — sort object indices by signed 16-bit Y coordinates ($0538/$0539) using the index table at $0377/$0378, then render each sorted object via `RenderSpriteRecordList` ($E77B).
### FinalizeOamAndPpuQueue ($C386)
- If `ObjectListFlag` ($C7) is set, skip status OAM; otherwise copy 20 bytes from $EBC2 into shadow OAM and select two extra bytes from $EBBC based on player selections.
- `TerminatePpuUpdateQueue` ($C3AA): write $00 to terminate the PPU command queue at $0300,Y.
- Hide unused sprites by writing $F0 to each remaining OAM Y coordinate (stepping by 4).
- `jsr AdvanceRandomState` ($E5F4), then `jmp CommonEngineExit` ($EB6A).
---
## 9. Batting and Pitching
### Pitching (RunPitcherWindupSequence, $C6044)
- `RunPitcherWindupSequence` animates the pitcher windup (frames selected via `SetWindupFrame`).
- `SelectPitchAction` ($C6139): reads controller input to choose the pitch action.
- `SelectPitchType` ($C6178): searches `PitchTypeThresholdTable` / `PitchDirectionThresholdTable` ($C5973/$C5980) to determine pitch type and direction.
- `AnimatePitchStart` ($C6229), `UpdatePitchMovement` ($C6321): apply pitch trajectory and controller-based horizontal/vertical input.
- Pitch tables: `PitchVelocityBiasTable` ($C6399), `PitchDelayTable` ($C6402), `PitchRenderFlagTable` ($C6405).
### Batting (SelectBatterAction, $C6414)
- `WaitForBatterAction` ($C6422): wait for batter input.
- `UseBatterControllerInput` ($C6464): map controller to batter action slot.
- `SelectActionFromObjectState` ($C6590) with `ObjectActionLookupTable` ($C6502).
- `DispatchActionType` ($C6614): route to the appropriate action handler.
### Object motion and collision
- `MoveControlledObject` ($D371): apply controlled horizontal/vertical offsets (`ControlledHorizontalOffsetTable` / `ControlledVerticalOffsetTable` at $D384/$D38C).
- `TestObjectCollision` ($D38B): object-vs-object collision testing.
- `TestCurrentObjectInput` ($D7D7): resolve controller input for the current object.
- Geometry helpers: `CalculateCurrentObjectGeometry` ($D7AE), `CalculateGeometryDifference` ($D566), `BuildGeometryVector` ($D74D), extensive trigonometry/vector math routines ($D5xx–$D8xx).
---
## 10. Audio Engine
### InitializeAudioEngine ($F9D0)
Initialize the APU: write DMC settings, configure pulse/triangle/noise channel presets, write pulse periods from tables ($FA00–), set up audio lookup data.
### UpdateAudioEngine ($FB05)
Per-frame audio update:
- Clear/finalize primary audio effects.
- Update pulse 1 and pulse 2 effects (preset effects, descending effects, state-change effects, phase effects).
- Update triangle pitch sweeps.
- Dispatch music and noise (`DispatchMusicAndNoise` at $FB65): update music sequencer, noise envelope, timed triangle effects, DMC output.
### Music
- `StartMusicSequence` ($FD34): initialize a music sequence by finding its bank and copying the descriptor.
- Channels: pulse 1 ($FD96), pulse 2 ($FDCF), triangle ($FE0B).
- Music sequence data tables at $FE60–$FFD0 (`MusicSequenceData_FE60` … `MusicSequenceData_FFD0`).
- Sound effect presets and noise/triangle envelopes throughout the $F9xx–$FE2x region.
Audio is advanced from the NMI via `jsr InitializeAudioEngine` ($F9D0) each frame; the broader `UpdateAudioEngine` ($FB05) is called from the event system.
---
## 11. Object Event System (the main-loop engine)
The game is driven by a **queued event system** rather than a simple polling loop:
- `InitializeQueuedEventSystem` ($EB9D): clear $0600–$06FF, queue count $E2, active slots, set current object index.
- `QueueInlineObjectEvent` ($EB55) / `QueueDecodedObjectEvent` ($EB2A): push an event onto the queue.
- `ProcessQueuedObjectEvent` ($EB2A): walk the queue and process active events.
- `DispatchNextQueuedEvent` ($EB66): pop the newest event, save its continuation address on the hardware stack (SP = $FF), and `rts` into it — this is how control transfers between event handlers.
- `CommonEngineExit` ($EB6A): mark the current object inactive; if the queue is empty, wait (`beq DispatchNextQueuedEvent_Wait`) polling $E2; otherwise disable NMI bit 7 and dispatch the next entry.
The main gameplay loop (`RunRoundStateLoop`) and most state handlers ultimately pass through `CommonEngineExit`, which blocks until the NMI or another producer queues the next event. This makes the NMI the real-time heartbeat and the main loop an event consumer.
---
## 12. PPU Streaming Helpers ($E9xx–$EBxx)
A family of routines drives PPU updates from inline byte streams embedded in the code:
- `DisablePpuRendering` ($E96C), `EnableNmiAndWaitForFrame` ($E978).
- `FillPpuMemoryWith26` / `FillPpuMemoryAfterValueSaved` ($E983/$E985): fill PPU memory with a byte value.
- `DecodeInlineParameters` ($EA09), `ReadInlineWord` ($EA28), `CopyInlineBytesToZeroPage` ($EA45).
- `WritePpuControlFromCarry` ($EA6E), `SetPpuAddressFromInlineRecord` ($EA97), `WritePendingPpuByte` ($EABF), `WriteInlinePpuBlock` ($EAC9), `PPUWriteString` ($EAFB).
- `CopyInlineDisplayRecord` ($EA17): draw an encoded text record (count byte followed by character data) — used heavily for titles, scores, round numbers, and announcements.
- `ProcessInlinePpuStream` ($EA63): process a stream of PPU commands.
Many routines are followed by `.byte` inline data that must **not** be disassembled as instructions; the disassembly marks these explicitly.
---
## 13. Match Completion and Results
### CompleteMatchState ($C6F7)
- Increment `RoundStateIndex`.
- If `InningOrSharedScore` ($66) is nonzero → `InitializeResultsPosition`; otherwise draw a single encoded character ($32).
- `InitializeResultsPosition` ($C708): set sign state to $80, draw a 10-character message (`$25,$10,$0A,$16,$0E,$25,$1C,$0E,$1D,$25`), set timing $FD = $4, dispatch event slot 2, queue inline `$02,$88,$C4`.
- `ResultsDelayLoop` ($C737): repeat a $3C-tick delay 10 times.
- Disable APU ($4015 = $00), clear `SessionActive` ($1E), then fall into `InitializeTitleScreen` ($C74A) to return to the title.
---
## 14. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C40B) ───► clear RAM $0000-$07FF, init sprite sort, disable PPU/APU
│
▼
jmp InitializeTitleScreen ($C74A)
│
├─ init event system, draw title screen
├─ EnableNmiAndWaitForFrame (NMI now runs each frame)
├─ WaitForTitleInputRelease (wait for START + input release)
▼
jmp InitializeSessionDisplay ($C4AA)
│
▼
jmp InitializeMatchState ($C4B9)
│
├─ Random0To5 → PlayerOneSelection ($34)
├─ Random0To5 (≠ P1) → PlayerTwoSelection ($35)
├─ derive ActivePlayerSide ($4B)
└─ WaitForPlayerSelection (spin until $43 < 0)
│
▼
CompleteMatchSetup ($C50C)
│
▼
InitializeRoundState ($C51E)
│
├─ enable audio, reset counters, init objects from table ($D06B)
├─ draw messages, select music cue
▼
ResetGameplayState ($C57F) → FinishGameplayReset ($C5F1)
│
▼
RunRoundStateLoop ($C5FE) ◄───────────────────┐
│ │
├─ InitializeObjectStateLookup │
├─ RunPrimaryRoundHandlers │
│ ├─ InitializeSecondaryActors │
│ ├─ InitializeConditionalObject │
│ ├─ walk RoundEventSlotTable │
│ └─ dispatch queued events │
├─ ApplyPendingScoreUpdate │
├─ StartPostRoundDelay (wait $48 ticks) │
├─ ProcessPostRoundEvents │
├─ CheckPostRoundContinuation │
│ ├─ match complete → results → title │
│ └─ AdvanceRoundNumber → next round │
└─ eventually → CommonEngineExit ($EB6A) │
wait for queued event ($E2) │
dispatch next event (rts into it)│
└────────────────────────────────┘
NMI ($C046) [every frame]
├─ PPU command queue flush ($0300)
├─ OAM DMA ($4014)
├─ scroll ($2005), PPU mask/ctrl ($2001/$2000)
├─ InitializeAudioEngine ($F9D0)
├─ controller latch + read (JOY1/JOY2)
├─ controller state (pressed/held edges)
├─ START branch → new session or command $0B
├─ QueueInlineObjectEvent + ProcessQueuedObjectEvent
├─ FrameCounter++, CalculateControllerAssignments
└─ restore registers, rti
```
---
## 15. Key Zero-Page / RAM Symbols
| Address | Symbol | Use |
|---------|--------|-----|
| $00–$01 | IndirectPtrLo/Hi | RAM-clear indirect pointer |
| $0D–$0E | NmiSavedX/Y | NMI register save |
| $0F | DerivedControl0 | Derived control input |
| $11–$12 | ControllerHeld1/2 | Currently held buttons |
| $13–$14 | InputState1/2 | Current input state |
| $15–$16 | ControllerPressed1/2 | Newly pressed buttons |
| $17–$18 | InputAux1/2 | Auxiliary input |
| $19–$1A | RandomStateLo/Hi | PRNG state |
| $1B | TransitionCounter | Transition delay |
| $1D | TransitionActive | Transition in progress |
| $1E | SessionActive | Session/game active flag |
| $1F | NmiStateFlag | NMI state flag |
| $20 | FrameState20 | Frame state |
| $21 | SelectedPitchAction | Current pitch action |
| $33 | RoundStateIndex | Current round/inning index |
| $34–$35 | PlayerOne/TwoSelection | Selected teams |
| $36–$39 | ObjectScreenX/Y (+Hi) | Object screen coordinates |
| $3E–$3F | PpuQueueOffset / OamWriteOffset | PPU/OAM write positions |
| $40 | ModeFlags | Display/mode flags |
| $42 | CurrentObjectArrayOffset | Current object slot |
| $43 | SelectionState | Selection state |
| $44 | PlayerSelectionActive | Player selection active |
| $45 | MatchCompleteFlag | Match finished |
| $46 | StateSequenceActive | State sequence running |
| $48 | DelayCounter | General delay |
| $4B | ActivePlayerSide | Active player (0/1) |
| $4D–$50 | RandomState0/1, RandomBits0/1 | Randomization state |
| $53–$56 | SavedBaseX/Y (Lo/Hi) | Saved base coordinates |
| $5C | BaseMotionActive | Base motion active |
| $5D | BaseMotionPhase | Base motion phase |
| $5E | BaseMotionEventCount | Base motion event count |
| $5F | TrajectoryResult | Trajectory result |
| $60 | PendingStateTransition | Pending state change |
| $66 | InningOrSharedScore | Inning / shared score |
| $67–$68 | PlayerOne/TwoScore | Scores |
| $69–$6A | EventWrite/ReadIndex | Event queue indices |
| $6F | PendingEvent | Pending event |
| $7E | ActionSequenceCounter | Action sequence counter |
| $83 | ObjectApproachCount | Approach counter |
| $86 | TrajectoryEvaluationEnabled | Trajectory eval enabled |
| $8E–$90 | RandomHorizontal/VerticalMotion | Random motion |
| $9D | CurrentActionState | Current action |
| $9F–$A1 | SecondaryActorState0–2 | Secondary actor states |
| $A2 | ObjectSequenceState | Object sequence state |
| $B7 | PositionSignState | Position sign tracking |
| $C7 | ObjectListFlag | Object list flag |
| $C9–$CA | SpriteSortBaseIndex / SpriteArrayEnd | Sort table bounds |
| $DD–$E0 | PpuCtrl/MaskShadow, ScrollX/Y | PPU/shadow/scroll state |
| $E1 | CurrentObjectIndex | Current object being processed |
| $E2 | ScriptDepth / queue count | Queued event count |
| $E3 | FrameCounter | Frame tick counter |
| $EF | GameplaySuspended | Gameplay suspended |
| $F6–$F7 | SoundPtrLo/Hi | Sound pointer |
| $FE | PendingAudioFlags | Pending audio flags |
| $FF | RenderStateFlags | Render state flags |
---
## 16. Notes on the Disassembly
- The source is **21,624 lines** and thoroughly annotated with symbolic names for nearly every routine, table, and data block — there are no generic `Label_Cxxxx` placeholders.
- Numerous routines are followed by inline `.byte` data blocks consumed by `QueueInlineObjectEvent`, `CopyInlineDisplayRecord`, or `ProcessInlinePpuStream`; these are intentionally left as raw bytes and marked in comments.
- The PRG is a single 16 KB bank at $C000–$FFFF; calls to $Dxxx/$Exxx/$Fxxx targets are all within the same bank (no bank switching).
- Build verification: `make` in `copilot/` produces `reconstructed_prg.bin` that is **byte-identical** to `prg.bin` and to the PRG bank of `baseball.nes`.