25 KiB
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:
sei/cld— disable maskable interrupts, clear decimal mode.- 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.
jsr InitializeSpriteSortTables($C7B1) — set up the sprite sort/index tables at $0377, $038C, $0399.jsr DisablePpuRendering($E96C) — turn off PPU output.- 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).
jsr ExpandRecordPointerList($C8BB) with inline data — builds record pointer structures.lda #$1A / sta PpuMaskShadow— initial PPU mask shadow value.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:
- Save X and Y into
NmiSavedX($0D) /NmiSavedY($0E). - Read PPUSTATUS ($2002) to reset the PPU address latch.
- OAM DMA — write $02 to OAMDMA ($4014) to copy $0200–$02FF to sprite memory.
- 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). - Apply scroll — write
ScrollX($DF) andScrollY($E0) to $2005. - Apply PPU registers — write
PpuMaskShadow($DE) to $2001 andPpuCtrlShadow($DD) to $2000. jsr InitializeAudioEngine($F9D0) — initialize/advance the audio engine.- Latch controllers — strobe JOY1 ($4016) with $01 then $00.
- Read both controllers — sample JOY1/JOY2 eight times each, shifting button states into
ControllerPressed1($15) /ControllerPressed2($16). - Update controller state — compute newly-pressed edges (
Pressed = (Pressed & ~Held) | raw) and updateControllerHeld1/2($11/$12). - START-button branch:
- If START pressed and
SessionActive($1E) is set →jsr ProcessQueuedObjectEventwith command $0B. - If START pressed and session is not active → set
TransitionActive($1D) andGameplaySuspended($EF), thenjmp InitializeNewSession($C49C).
- If START pressed and
NMI_CheckState— ifNmiStateFlag($1F) is nonzero, skip ahead and restore registers.- Otherwise
jsr QueueInlineObjectEvent($EB55) with inline command bytes$05,$21,$C1, thenjsr ProcessQueuedObjectEvent($EB2A) with command $0B. inc FrameCounter($E3).jsr CalculateControllerAssignments($C87E) — derive per-player control state.- Store derived input into
InputState1/2($13/$14) andInputAux1/2($17/$18). - 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:
jsr InitializeQueuedEventSystem($EB9D) — clear the $0600–$06FF event page, the queue count ($E2), active-event slots, and setCurrentObjectIndex($E1) = 1.GameplaySuspended($EF) = 0;TransitionActive($1D) = 1.PpuCtrlShadow= $80; disable PPU rendering; fill PPU memory (FillPpuMemoryAfterValueSavedat $E985 with value $25).jsr ProcessInlinePpuStream($EA63) twice — streams title PPU data (inline records$8D,$F8and$00,$C0).jsr EnableNmiAndWaitForFrame($E978) — enable NMI and synchronize to a frame.- Enable APU channels ($4015 = $0F), set timing byte $FD = $08.
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.
- Update
- On exit:
TransitionActive= 0,GameplaySuspended= 1, clear audio ($4015), re-init the event system, queue an inline command$03,$88,$C4, thenjmp 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
$053BandPositionSignState($B7) to $80. jsr EnableNmiAndWaitForFrame.- Player/team selection:
Random0To5($C850) →PlayerOneSelection($34).- Repeatedly
Random0To5until a value different from player one →PlayerTwoSelection($35).
- If
GameplaySuspendedis set, skip side selection; otherwise deriveActivePlayerSide($4B) from mode flags or a random bit. - Queue inline object events
$03,$19,$C8and$02,$19,$C8, setPlayerSelectionActive($44) = 1. WaitForPlayerSelection($C4FF) spins until$43(SelectionState) becomes negative, then queues$03,$3E,$C4and falls intoCompleteMatchSetup($C50C).
CompleteMatchSetup ($C50C)
FillSelectionBackgroundBlocks($C85A),QueuePlayerSelectionDisplay($C7DD),InitializeRandomBitfields($C899),QueuePlayerValueDisplay($CF8B).- Queue inline command
$09,$37,$CE, then enterInitializeRoundState($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) andRoundStateIndex.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 onRoundStateIndexparity → store in $FD.PrepareRoundObjects($C573): queue player-selection display, queue random-state record, clearMatchCompleteFlag($45) andSecondarySequenceState($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, setStateSequenceActive($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
RoundStateIndexand score comparison ($67 vs $68), either jump toCompleteMatchState($C6F7) or finish the reset.
FinishGameplayReset ($C5F1)
InitializeDefaultObjects($D030), clear sign state, select event bank 1 → enterRunRoundStateLoop($C5FE).
RunRoundStateLoop ($C5FE) — main per-round loop
This is the core gameplay loop:
- If
PendingStateTransition($60) is set →InitializeObjectStateLookup($C8A3). - If either
PendingStateTransitionorStateSequenceActive($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_Exchangewith command $08.- Walk the 22-entry
RoundEventSlotTable($C6E1) in reverse, dispatching each viaDispatchNextQueuedEvent($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 forPendingEvent($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 onRoundStateIndexparity.CheckPostRoundContinuation($C6C5): if match complete →AdvanceRoundNumber($C6CC); otherwisejmp ResumeRoundSetupAtRandomDirection($C5A4).AdvanceRoundNumber($C6CC): update counters/display, incrementRoundStateIndex; if index reaches $12 →InitializeResultsPosition($C708); otherwiseSelectRoundMusicCue($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); otherwiseIntegrateAllObjectPositions($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 inPositionSignState($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
TransitionActiveis set: copy an 88-byte sprite/OAM template from $F94D into shadow OAM at $0200, setOamWriteOffset($3F), and jump toTerminatePpuUpdateQueue. - 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 onModeFlags. - 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 viaRenderSpriteRecordList($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), thenjmp CommonEngineExit($EB6A).
9. Batting and Pitching
Pitching (RunPitcherWindupSequence, $C6044)
RunPitcherWindupSequenceanimates the pitcher windup (frames selected viaSetWindupFrame).SelectPitchAction($C6139): reads controller input to choose the pitch action.SelectPitchType($C6178): searchesPitchTypeThresholdTable/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) withObjectActionLookupTable($C6502).DispatchActionType($C6614): route to the appropriate action handler.
Object motion and collision
MoveControlledObject($D371): apply controlled horizontal/vertical offsets (ControlledHorizontalOffsetTable/ControlledVerticalOffsetTableat $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 (
DispatchMusicAndNoiseat $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), andrtsinto 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 intoInitializeTitleScreen($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_Cxxxxplaceholders. - Numerous routines are followed by inline
.bytedata blocks consumed byQueueInlineObjectEvent,CopyInlineDisplayRecord, orProcessInlinePpuStream; 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:
makeincopilot/producesreconstructed_prg.binthat is byte-identical toprg.binand to the PRG bank ofbaseball.nes.