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

25 KiB
Raw Blame History

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.