MUSCLE (U): decode the bytecode VM layer (Pass 3b)

The 42 token programs were the last undecoded part of MUSCLE. Each now carries a
token-by-token listing above its unchanged .byte data, generated by
_tools/bytecode_dis.py and wired into the build as 'make pass3'.

Encoding, derived from RunBytecodeInterpreter ($9AE8) and BytecodeHandlerTable
($9237): the dispatch is one byte per element. A byte in $C8-$EF selects one of
the 40 handler-table entries; every other byte takes the literal path at $9B08
and is pushed onto the operand list at $0033[Y], accumulated into $003C[Y].

The one exception is four opcodes whose handler's first instructions are
'lda ($1A),Y / iny', so they consume the following byte inline:
  $CB LOOP_BEGIN  -> $7C,X count + $7E,X body address
  $D1 SET_RATE    -> $43,X
  $D7 DRAW_SPRITE -> count for the $A660 blit
  $E1 SET_WAIT    -> $7A,X
$CB and $C9 are the loop pair: LOOP_END decrements $7C,X and, if non-zero,
reloads Y from $7E,X, rewinding the interpreter to the top of the body.

Decoded 377 opcodes and 937 literal operands across 42 programs; all 40 defined
opcodes are used, so the opcode set is complete.

Two standalone programs contain no byte in $C8-$EF at all, so every byte takes
the literal path - the listing states that rather than implying code.

Each mnemonic was read off its handler's own straight-line path up to its first
'jmp $9AE8' / 'jmp $9B25'. Handlers that only tail-call a game routine are named
for the routine rather than guessed at, since the semantics live in the callee.

bytecode_dis.py inserts comment lines only; .byte data is copied through
verbatim. Verified: all 16,384 PRG bytes still emitted exactly once, every value
equal to the ROM, zero audit mismatches, and 'make regen' (pass1 -> pass2 ->
pass3) reproduces prg.s byte-for-byte. Byte-identical ROM verified.

MUSCLE (U): trace the stage payload with py65 (Pass 3c)

The stage payload at $B0FB-$BEBF could not be decoded statically, and the
trace confirms why: across all 16 KB of code there is exactly ONE operand in
that range - $BD28, the literal pointer table itself. The real address is
formed at runtime by 'adc $BD28,X', where A is a value carried in the
bytecode stream, so the readers are indexed by a runtime value and cannot be
enumerated by searching the code.

FCEUX cannot observe this either: its Lua API offers registerexec /
registerexecute / registerrun / registerwrite but no memory-read hook, and with
no static list of readers there is nothing to hook.

_tools/trace_stage_payload.py therefore uses py65's
ObservableMemory.subscribe_to_read, which fires on every read of a range and
can report PC from inside the hook. py65 is only a 6502 core, so the tracer
adds the minimum hardware to boot the game: a scanline counter off
processorCycles, $2002 vblank at scanline 241 cleared on read, NMI at the same
scanline, $4014 OAM DMA, and the $2005/$2006/$2007 latches. Without NMI the
game never leaves $80E0 (lda $0321 / beq), because $0321 is the flag the
bytecode VM raises.

Results over 300 frames (26.8M cycles, 4,071 NMIs): 2,548 payload reads from
44 distinct reader PCs, touching 914 of 3,421 bytes.

Findings:
- The hottest reader is $AC82, the compressed-VRAM RLE decoder: 1,046 of its
  1,128 reads advance by exactly +1, comparing each byte against $C0.
- $9B50 is a backward copy (lda ($12),Y / sta ($10),Y / dey), stepping -1.
- 107 distinct 8-byte blocks had all eight bytes read, with spacing dominated
  by $08 - a dense 8-byte record array - plus a sparser group on a $D0 stride
  ($BBE0, $BCB0, $BD80, $BE50).
- Readers use three disciplines: forward sequential, backward sequential, and
  variable stride consistent with a fixed-offset field walk.

py65 is deliberately NOT a build dependency: the tool documents unpacking the
wheel on demand. 'make regen' is unaffected. Byte-identical ROM verified.

MUSCLE (U): fix the NMI/controller bugs in the payload tracer; correct the Pass 3c model

The Pass 3c tracer had two bugs that were suppressing all progress, both now
fixed in _tools/trace_stage_payload.py (previously scratch, now durable):

* NMI re-armed by $2002 reads. The vblank flag must be cleared once per frame,
  not on every read of $2002 -- the main loop polls $2002 and spins on the
  bytecode VM flag at $8226, so each poll cleared bit 7 and re-armed NMI inside
  the same scanline. That gave 40,900 NMIs across 3,000 frames and corrupted all
  game state. A per-frame nmi_fired flag restores exactly one NMI per frame
  (verified: 600 frames -> 600 NMIs).

* Input went to $4017. The poll routine at $AA69 is `lda $4016,X` with X=1 and
  only compares bits 0-1, so driving $4017 consumed nothing -- all masks tested
  gave byte-identical traces. The game consumes joy1 on $4016 and needs a real
  latch/shift register. With it, START matters: 47.7% coverage vs 39.0%.
  Match entry is two discrete START presses (title -> char select -> match).

Coverage 26.3% -> 40.9%. Both bugs and the port subtlety are documented in the
tool header so they are not re-derived.

Corrects the Pass 3c conclusion. $B0FB-$BEBF is NOT per-stage level data:

* $BD28 is an 8-entry per-ACTOR entry-offset table, not a stage table.
  $A063 `adc $BD28,X` with X = ActorIndex ($1E), plus #$32 at $A071 when
  PlayerActiveFlag ($78,X) is set. Observed actors are $00-$04.
  Forcing all 8 actor indices adds zero new bytes.

* The sound engine reads the same window: $B03C `lda (SoundReadPointerLo),Y`
  with `cmp #$F0 / sbc #$F0 / tay / lda $B0E3,y` is note decoding, which is
  where the $FF/$F0 high-nibble terminators come from.

So the window is a shared pool -- per-actor bytecode, compressed-VRAM RLE, and
music note streams -- and stage cycling cannot raise coverage past ~40%. The
remaining 2,161 B need other music tracks and scripted match play, not taps.

Also corrects two stale claims: $0326 is not a level byte (never leaves $00 even
in a confirmed match), and $0320 is a boolean, not a screen-state machine.

Build unaffected: byte-identical, and `make regen` still reproduces prg.s
byte-for-byte.

MUSCLE (U): promote to the top-level inventory as #16

git mv _wip/MUSCLE (U) -> MUSCLE (U). The relative symlink
copilot/rom.nes -> ../M.U.S.C.L.E. (U).nes survives the move untouched.

* copilot/Makefile: tool paths ../../../_tools/ -> ../../_tools/ for NESDIS,
  PASS2 and BYTECODE. `make` prints the byte-identity message and `make regen`
  still reproduces prg.s byte-for-byte, both verified after the move.
* MUSCLE (U)/OVERVIEW.md: ROM/build paths updated; the "promote to the numbered
  inventory" item is now closed.
* Root OVERVIEW.md: added as #16, removed from the _wip skeleton table
  (20 -> 19), added a ### 16 detail section, and its vector/mapping notes moved
  out of the skeleton section.

All 16 top-level projects verified byte-identical.

Add Pass 1 verification tool; teach nesdis.py the undocumented opcodes

_tools/verify_pass1.py verifies Pass 1 across every top-level project in two
independent directions:

  * Faithful  - does the source still rebuild its ROM byte-identical?
  * Complete  - does the committed source claim as much code as a fresh trace
                can actually reach?

The second is the one that catches real problems. A project can reproduce all
16,384 bytes perfectly while calling 95% of them data, because the tracer gave
up early. Instruction *counts* are compared rather than bytes, so the check is
independent of comment style (which is not uniform across the repo).

Result: all 16 projects are faithful, and Joypad Test Cartridge is the only
truncated Pass 1 -- 282 committed instructions against 1,773 reachable.

nesdis.py gains three fixes needed to make that verdict trustworthy:

* --undocumented (default OFF) decodes the 105 undocumented NMOS opcodes.
  They are used as NOP padding and as real RMW ops, and a tracer that halts on
  them loses most of the code: Joypad goes 580 -> 3,846 code bytes. Default off
  so MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

* Undocumented opcodes are emitted as explicit `.byte` with the disassembly in
  the comment. ca65 V2.19 implements none of them, so they cannot be written as
  mnemonics; keeping them in the code region preserves the code/data split and
  the audit trail. Official 0xEA NOP still encodes normally.

* Two latent bugs, both now reachable:
  - RESET and IRQ are commonly the same address, and the semantic-name map was
    overwriting, renaming the primary entry point to `IrqHandler`. Now
    setdefault, in RESET/NMI/IRQ priority order.
  - render() stamped every branch operand as Label_XXXX and never reconciled it
    with the semantic name map, so a branch into a named routine emitted a label
    that was never defined and the file would not assemble.

Joypad Test Cartridge (U): redo Pass 1, complete Pass 2; merge _wip into the inventory

Pass 1 (redone). The committed split claimed 282 instructions / 580 bytes (3.5%).
The tracer halted at $C09E on `DCP $3C20` ($CF) -- an undocumented opcode used as
ordinary code -- and filed 3,256 bytes of real code as `.byte` data.

Now 1,773 instructions / 3,836 bytes (23.4%), a 6.6x increase. Verified against a
fresh trace: 1,769 emitted instructions + 4 undocumented opcodes written as
explicit bytes = 1,773, an exact match. Byte accounting stays exact at
3,836 + 12,548 = 16,384.

ca65 V2.19 implements none of the undocumented opcodes, so the 4 affected
instructions (DCP $3C20, LAX $20D4, NOP $C1,X, RRA ($D0),X) are emitted as
explicit `.byte` with the disassembly in the comment. They stay in the code
region, so the split and the audit trail are both preserved.

Pass 2 (complete). All 202 labels renamed; zero `Label_Cxxxx` remaining. Renames
are keyed by address and verified to have bound to the intended address, not just
to some address. 202 routine headers, every one describing real behaviour -- no
"undetermined in Pass 1" boilerplate left. `locals.inc` carries 99 aliases, the
proven semantic ones sitting beside the generated `ZpXX` entries per the alias
pattern in AGENTS.md.

Two naming errors in the previous draft are corrected: $55 is MarkerValue (it
holds $70/$88/$A0 and is compared against them), not a "P1 Down timer"; and the
twelve MarkerVar variables are named by position because which player/button
each belongs to is not established.

Pass 3 is now outstanding, and the old Pass 3 was wrong: it described
$C09E-$C173 and $C17F-$CCF6 as data, and both are code. After the redo there is
exactly one data region, $CF06-$FFFF, confirmed by orphan scan.

Documentation.

The root inventory now lists all 36 projects in one table -- the 20 `_wip/`
projects are rows 17-36 rather than a second table that could drift. That second
table had wrong mirroring for 12 of them (Duck Hunt, Golf, Hogan's Alley, Ice
Climber, DK Jr Math, Lode Runner, Lunar Pool, Mario Bros, Millipede, Pyramid,
Raid [a2], Urban Champion were all recorded H when they are V, and vice versa);
every value is now read from the ROM rather than trusted.

Build status is split into two honest tiers: rows 1-16 are BYTE-IDENTICAL and
compare the whole ROM, rows 17-36 are PRG-IDENTICAL because their Makefiles only
run `cmp reconstructed_prg.bin prg.bin`. Header and CHR are not proven for those.
All 36 build cleanly; the WIP count was also corrected from 19 to 20.

`_tools/verify_pass1.py` checks all 36 and reports none truncated.

Mario Bros (JU): Pass 1 disassembly, 14.6% -> 72.2% code

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 5,735 instructions, 11,829 code bytes (72.2%), 937 labels and
routine headers, byte accounting exact at 11,800 + 4,584 = 16,384, and
BYTE-IDENTICAL against prg.bin.

Coverage came from four steps, not one:

  vectors + direct jsr/jmp/branch, official opcodes   1,185 instr   14.6%
  --undocumented (105 extra NMOS opcodes)            1,210 instr   14.9%
  --auto-seed (61 table-referenced entries)          4,187 instr   51.1%
  --extra-seed 0xC0B6,0xC5A3                         5,735 instr   72.2%

Almost everything sits behind computed dispatch. Three mechanisms are now
documented in the project OVERVIEW:

* $CD9E is a stack-based indirect call: it pops the table address with `pla`,
  indexes it by A*2 with `lda ($14),y`, then `jmp ($0014)`. Because the pointer
  arrives on the stack it cannot be followed statically from the dispatcher.
* $C5A3 is a 20-entry table of `JSR` thunks ending in `rts`. Probing it linearly
  gives twenty valid targets, and seeding it recovers most of the last 21%.
* $C0B6 is the NMI phase dispatcher, reachable only by computed dispatch.

Two overlapping-decode bugs in _tools/nesdis.py had to be fixed first:

* Control-flow targets must win over fall-through. `bne` at $C4BA targets $C528,
  but the fall-through path had decoded $C527 as a 2-byte `NOP #$A5` whose
  operand swallowed $C528. No assembler branches into the middle of an
  instruction, so the spanning decode is now dropped in favour of the target;
  its bytes fall out as data, leaving byte accounting unchanged.
* Branch targets inside a surviving instruction must be pruned from the target
  set. Otherwise emit_code's linear walk consumes those bytes as an operand, so
  the label is referenced but never defined and the file does not assemble at
  all -- `Error: Symbol 'Label_C528' is undefined`.

Neither change disturbs an existing pipeline: MUSCLE's `make regen` still
reproduces prg.s byte-for-byte, Joypad Test Cartridge still builds byte-identical,
and _tools/verify_pass1.py reports all 16 top-level projects faithful with none
truncated.

Mario Bros (JU): Pass 2, partial -- make the call graph readable

Two mechanical wins, in order of value:

* **477 jsr/jmp operands rewritten from raw $XXXX to labels.** The tracer emits
  call and jump targets as hex, so the call graph was unreadable -- the most
  called routine appeared as `jsr $CCA0` 14 times over. Every one of those sites
  already had a label at its target address, so the substitution is purely
  mechanical and byte-preserving. Zero raw hex call operands remain.

* **13 landmark routines named** from verified instruction evidence:
  WaitVblank1, ClearRamPageLoop, NmiPhaseDispatch ($C0B6), JsrThunkTable
  ($C5A3), DispatchViaStackPointer ($CD9E), AdvanceDispatchPointer ($CDB4),
  PushTablePointerDispatch ($CCA0), DispatchTableF393 ($CAB9),
  InitSpritesOffscreen ($DFC4), AdjustDecimalColumns ($D15A),
  TestRamFlagPair0500 ($D328), NextIndex ($CE84).

**922 labels remain unnamed, and that is recorded as the honest state** rather
than papered over. They sit 15-25 per page across $CC00-$CFFF, which is just
the game logic: naming them means reading ~5,700 instructions across 935
routines. Guessing from the call graph and the NES convention would be exactly
the guessy rewrite PROMPTS.md rules out.

The productive next step is more reach, not more naming. The 4,555 unreached
bytes are where most unnamed routines live, and they are gated behind the
stack-pointer dispatch at $CD9E, whose pushed table addresses are only known in
the callers' frames.

Still BYTE-IDENTICAL; all 16 top-level projects remain faithful.

Record audit-comment work for Excitebike and Pac-Man as a TODO

Both projects are hand-written sources with semantic labels but no
byte-identity audit comment, so a source line cannot be mapped back to exact
ROM bytes without re-assembling. Every other project in the repo carries one.

Documented what was tried and why it was dropped rather than left implicit.
Addresses from 'ca65 -l' work fine -- the listing offset indexes prg.bin
directly -- but the listing carries no source line numbers, so entries cannot
be keyed back to source lines. Byte values also have to come from prg.bin
rather than the listing, since ca65 prints 'rr rr' for relocatable words.

No tool was committed, because the one written does not work. Both projects
verified still byte-identical; no source file was changed.

Add TODO.md handoff; bring AGENTS.md back in line with the repo

AGENTS.md had drifted on facts that are now wrong, not just incomplete:

* "Eleven disassemblies" -> 36 (16 top-level, 20 in _wip). The index it points
  at had the same stale count.
* The audit-comment convention was stated as `; PRG:$xxxx AA BB`, but the
  dominant spelling is `; CPU:$xxxx PRG:xxxx  AA BB` and five other variants are
  in use, with Excitebike and Pac-Man carrying none at all. Now stated accurately
  as "preserve whatever spelling a file uses", with the gap pointed at.
* `_tools/` was described as holding splitnes.py and chr2gif.py. It holds six
  tools now, including the Pass 1 tracer and the verifier.
* ROM filenames "contain spaces" -> spaces *and parentheses*, which is also what
  makes FCEUX hang headless.

Added, because each one costs real time if rediscovered:

* The two build tiers. The 20 _wip projects only `cmp reconstructed_prg.bin
  prg.bin`, so their header and CHR are NOT proven -- calling them
  byte-identical to the ROM is wrong.
* A promotion procedure, since that has now been done twice.
* A tracer/toolchain gotchas section: undocumented opcodes truncating traces
  (and ca65 being unable to encode them), vectors living at the end of the PRG
  rather than in the iNES header, the RESET==IRQ name collision, control-flow
  targets beating fall-through, the branch-target pruning that stops a file
  failing to assemble, NMI firing once per frame rather than per $2002 read,
  joy1 being $4016, and ca65 listings carrying no source line numbers.

TODO.md is the handoff document: open work per project with enough detail to
resume, the residue counts, and the reasoning behind each abandoned approach so
it is not silently retried.

Nothing rebuilt or changed; all projects still verify.

Joypad Test Cartridge (U): Pass 3 complete — data region named and VRAM command stream decoded

The monolithic `.byte $CF06-$FFFF` block (12,538 bytes) has been broken into 20 named sub-blocks:

VRAM command streams ($CF06-$D4AD): three command streams separated by $FF terminators, read by VramWriteLoop via RetAddrUpdate. Opcode set decoded: $4C = jump, $60 = deferred PPUCTRL, bit-7-clear = VRAM write (PPU addr + data, bit 6 of data = long/short selector).

OAM tables ($D4E3-$D6A3): 12 groups of 32-byte tables (8 entries of Y/tile/attr/X), read by CopyOamFromTable ($D4E3) and CopyOamFromTable70 ($D5C4).

Sprite templates ($D6A5-$D6E4): sprite template read by CopySpriteTemplateTable ($D6A5) and button OAM template read by PrepareButtonOam ($D6C5).

Padding: $FF ROM padding ($D6E5-$FFF9) and vectors ($FFFA-$FFFF).

OVERVIEW.md updated: section 8 documents all 20 sub-blocks with the VRAM command stream format and OAM table structure; section 11 marks Pass 3 complete with remaining work (MarkerVar player/button mapping, HandleButtonPress sprite order).

Build verified byte-identical.

Root OVERVIEW.md: mark Joypad Pass 3 complete

Update summary table (row 14: Pass Pass 2 → Pass 3 complete), detail section
(section 14: Pass 2/3 → Pass 3 complete with VRAM command stream decoded),
methodology note table (Pass 2 complete → Pass 3 complete), and notable
observations (Joypad at Pass 2/3 → Pass 3 complete).

Joypad Test Cartridge is now fully Pass 3 complete: all 20 data sub-blocks
named, VRAM command stream opcode set decoded, OAM table structure documented.

TODO.md: mark Joycad Test Cartridge Pass 3 complete

Mario Bros (JU): Pass 2 substantial — 252 subroutine entry points named

All 252 jsr/jmp targets resolved from raw $XXXX to labels:
- 90 semantic names: audio/sound routines ($F8xx-$F9xx: AudioPulse1CtrlSetup,
  WritePulse2Freq, ProcessTriangleChannel, SetupAudioChannel, etc.), gameplay
  helpers (CheckZpB3Return, CheckRangeReturn012, ToggleZpFC_Bit3, etc.)
- 165 address-based names (SubCxxxx) for the remainder
- Zero raw $XXXX call operands remain

667 Label_Cxxxx internal branch targets remain — these are local control-flow
destinations within routines, not subroutine entry points, and do not need
semantic names for the call graph to be readable.

Build verified BYTE-IDENTICAL. Root OVERVIEW.md updated (row 26: Pass 0 skeleton
→ Pass 1 complete, Pass 2 substantial). TODO.md updated.

Lunar Pool (U): Pass 1 complete, 57.8% code; nesdis.py opcode/emitter fixes

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 4,586 instructions, 9,475 code bytes (57.8%), 6,909 data bytes,
byte accounting exact at 9,403 + 6,981 = 16,384, BYTE-IDENTICAL.

Coverage: vectors + direct jsr/jmp/branch reached 1,259 instructions (15.6%).
--undocumented took it to 4,572 (57.6%), and --auto-seed (48 table-referenced
entries) plus --extra-seed 0xE80C (the non-$C000 RESET vector) took it to
4,586 (57.8%).

Three undocumented opcodes were missing from nesdis.py and are added:
* $72 RRA abs (the RRA family was missing the $72 absolute form)
* $42 NOP #imm (the NOP-immediate family was missing $42)
* $FA NOP abs,X (the NOP-abs,X family was missing $FA)

One emitter fix: ca65 silently rewrites an operand whose high byte is $00 in
the shorter zero-page form -- `sta $00FF,x` assembles as `sta $FF,x` ($95
instead of $9D). render() now emits the `a:` prefix for ABS/ABSX/ABSY/IND
operands with a high byte of $00, forcing the absolute form so the emitted
bytes match the ROM. operand_value() strips the prefix when parsing.

These changes are general and verified not to disturb existing pipelines:
MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

Remaining gaps: 6,909 bytes (42.2%) still data, gated behind jmp ($7961) at
three sites (RAM-pointer dispatch; the blocks after them hold the screen text
"strings" "game over" and "hi`score__") and stack-based dispatch at $CBDC --
the same pattern as Mario Bros (JU)'s $CD9E.

TODO.md: add Lunar Pool as an open item

Adds comments for all lines to Pacman and Excitebike
Moves Pacman 'locals' and 'symbols' definitions into the right places
This commit is contained in:
tmerritt committed 2026-10-17 17:36:28 -04:00
1 parent e9caa8cb36
commit 837007843d
50 files changed
+62939 -13111

No files matched your search

+228
View File
@@ -0,0 +1,228 @@
0x8000 Reset
0x800E WaitForFirstPpuWarmup
0x8019 WaitForSecondPpuWarmup
0x802C InitializeGameState
0x8052 UpdateNametableFromStream
0x80AC StartStageSequence
0x8132 UpdateFadeFlashSprites
0x8164 InitializeRound
0x8226 MainGameLoop
0x82AC DivideBy20
0x82E1 SilenceSoundChannels1And3
0x82ED SilenceSoundChannel3
0x82FA StartIntroMusic
0x82FF StartMusicSequence
0x832A UpdateHudTallyTiles
0x8353 UpdateScoreTallyDigits
0x8372 WriteScoreTallyDigits
0x8399 LoadOamShadowFromTable
0x83A7 UpdateActorPositions
0x845C UpdateRoundTimers
0x856C PlayRoundChangeMusic
0x8596 UpdateScrollTarget
0x8598 UpdateScrollTargetAlt
0x8639 BeginVblankFrame
0x8644 ForceRenderingOff
0x8653 SetRenderingOffAndSignalVblank
0x865D UpdateRoundLogic
0x86CB NmiHandler
0x8751 FinishNmiUpdates
0x8759 FinishNmiRegisters
0x8764 IrqHandler
0x8765 SetSoundSequenceChannel1
0x877C RunGameStateDispatcher
0x87BF UpdateTimersAlt
0x87D1 PollControllerStart
0x87F2 UpdateFlashCounter
0x8837 PlayDeathSequence
0x8848 PollControllerBits
0x885B PollControllerAndAdvance
0x886C PollControllerBitsAlt
0x887C SignalVblankComplete
0x8882 PollControllerAndCheckRound
0x88A4 BuildOamList
0x88B0 EmitOamEntry
0x88EB EmitOamBlankEntry
0x88F9 EmitOamFromTable
0x88FF EmitOamAtOffset
0x8936 FindFreeOamSlot
0x8986 CompactOamList
0x89CE ReadControllerAndInit
0x8A28 UpdateStageActors
0x8A5E SpawnStageActors
0x8AE8 UpdateAttributeTable
0x8B07 ClearScrollLatch
0x8B20 WriteNametableTile
0x8B33 ClearAttributeRegion
0x8B92 RenderHudDigits
0x8BEF RenderHudDigitDescriptor
0x8BF8 SetRenderingOn
0x8C05 AdvanceAnimationSlot
0x8C0E QueueVramTransfer
0x8C11 QueueVramTransferAlt
0x8C65 ExecuteVramTransfer
0x8CB8 QueueVramDescriptor
0x934B UpdateActorX
0x9371 UpdateActorTile
0x9374 UpdateActorTileAlt
0x94B1 TestPlayerFacing
0x94C7 SelectActorSlot
0x94C8 SelectActorSlotAlt
0x94CF ClearActorPosition
0x94D7 ClearActorFields
0x94FE SetActorFlag
0x9585 UpdateActorState
0x95AE ComputeActorLeftEdge
0x95D2 ComputeActorRightEdge
0x95D9 CompareActorToLimit
0x9653 SetActorTileFromPalette
0x9667 StoreActorTile
0x969E AdvanceActorCounter
0x96AA SetActorTileValue
0x96C8 TestActorCollisionFlag
0x96F7 ComputeActorLeftEdgeAlt
0x9722 ComputeActorRightEdgeAlt
0x9729 TestActorRange
0x9732 MoveActorByStep
0x97BC SetActorAnimation63
0x97C0 SetActorAnimation62
0x97C4 SetActorAnimation61
0x97D5 UpdateActorAnimation
0x97FC TestAnimationSlot
0x982A ComputeActorLeftEdgeAt
0x9858 ComputeActorRightEdgeAt
0x985F TestActorValue
0x98E6 ComputeActorLeftEdgeAlt2
0x9911 UpdateActorTileFromIndex
0x9918 UpdateActorTileFromIndexAlt
0x99A3 SetSoundSequenceChannel2
0x99E2 LookupActorTileId
0x99E4 LookupActorTileIdAlt
0x9A24 StoreActorCoordinates
0x9A2E StoreActorCoordinatesAlt
0x9A3F FlipActorFacing
0x9A4E SpawnActorPair
0x9A6B SpawnActorPairAlt
0x9AE8 RunBytecodeInterpreter
0x9B05 DispatchThroughPointer
0x9B25 RunBytecodeProgram
0x9CBB ResetBytecodeState
0x9D4E CopyActorCoordinates
0x9DD6 SetActorBounds
0x9DE6 LoadActorTileIndex
0x9E6E UpdateActorFacing
0x9E7F QueueNametableWrite
0x9F14 FlushNametableWrite
0x9F4B UpdateActorMovement
0x9F54 MoveActorByOpcode
0x9FDF AddToNametableValue
0xA002 SubtractFromNametableValue
0xA01D ProcessBytecodeOpcode
0xA085 ComputeBytecodeOffset
0xA0A5 ResetBytecodeAccumulator
0xA0C6 ResetBytecodeAccumulatorAlt
0xA0F4 AdvanceBytecodeProgram
0xA0FB ResetBytecodeStateAlt
0xA12E ComputeActorTileRow
0xA15D ResetBytecodePointers
0xA17F RunActorScript
0xA250 LoadActorConfigByte
0xA253 ComputeActorConfigOffset
0xA25F CopyConfigBytes
0xA270 LoadActorConfigRecord
0xA298 CompareConfigValue
0xA2C8 ReturnFromConfigLoad
0xA2C9 LoadActorConfigFields
0xA316 StoreActorPaletteByte
0xA32C StoreActorControlByte
0xA338 ReadActorConfigByte
0xA435 CompareActorConfigValue
0xA46F StoreActorConfigResult
0xA482 LoadActorConfigAlt
0xA49F StoreActorConfigResultAlt
0xA4AA LoadActorFacingFlag
0xA4C7 LoadActorConfigBlock
0xA4DF TestSoundChannelState
0xA4F2 SetSoundSequenceChannel2Alt
0xA4FF FlipObjectIndex
0xA508 LookupObjectIndex
0xA512 UpdateObjectSlots
0xA51B AdvanceObjectSlot
0xA5DD WriteNametableBytes
0xA660 SetSoundSequenceChannel4
0xA68E UpdateObjectList
0xA695 UpdateObjectListEntry
0xA6F8 UpdateObjectCollisions
0xA79F CheckObjectScroll
0xA82F ClearScrollLatchAlt
0xA834 UpdateObjectSprite
0xA884 SetObjectFlags
0xA8D1 AdvanceObjectFrame
0xA8E9 TestObjectActive
0xA949 UpdateObjectAnimations
0xA952 UpdateObjectAnimation
0xA992 UpdateObjectTimer
0xA99E DecrementObjectCounter
0xA9AB UpdateObjectState
0xA9D8 ToggleScrollRegionFlip
0xA9FF BuildObjectList
0xAA56 ReadControllers
0xAA63 ReadControllerShiftRegister
0xAAB8 StoreControllerButtonBits
0xAACD EnableRendering
0xAADC DisableRenderingAndClearNametables
0xAAEF ClearNametablePattern
0xAB68 CopyPaletteFromTable
0xAB98 LoadShadowOamBlock
0xABA3 FillShadowOamBlock
0xABB1 UploadPalette
0xABE4 ApplyPpuScrollAndControl
0xABF7 ShiftAccumulatorRight4
0xABFC ClearOamShadow
0xAC0D SelectLevelVramWriter
0xAC3B SetLevelVramStream
0xAC43 FillAttributeBlock
0xAC4C WriteAttributeBlock
0xAC62 LoadAttributeStream
0xAC78 SetPpuAddressHighAnd40
0xAC82 WriteCompressedVramStream
0xAC88 AdvanceSourceByte
0xAC96 DecodeVramStreamToken
0xACA7 WriteVramRunToken
0xACC9 AdvanceDestinationWord
0xACD7 AdvanceCountWord
0xACE5 WithRenderingEnabled
0xACF5 WriteCompressedVramBlocks
0xAD19 ComputeCollisionBits
0xAD36 ComputePlayerOverlapBits
0xAE45 SetDefaultOverlapBits
0xAE9B ComputeEdgeOverlapBits
0xAF25 InitializeSoundEngine
0xAF3B UpdateSoundFrame
0xAF40 UpdateAllSoundChannels
0xAF43 UpdateSoundChannel
0xAF55 AdvanceSoundChannelNote
0xAF69 WriteSoundChannelState
0xAF6B RebuildApuEnableMask
0xAF73 StartSoundChannelNote
0xAFAB AdvanceSoundSequencePointer
0xAFC5 ComputeEnvelopeMask
0xAFD9 ComputeTonePeriod
0xB000 ShiftNoteTableIndex
0xB007 WriteToneRegisters
0xB029 WriteFixedToneRegisters
0xB03A ReadNoteDuration
0xB049 StoreNoteDuration
0xB059 StoreShortNoteDuration
0xB05D BuildApuChannelMask
0xB062 WriteApuEnable
0xB06C ApplySoundCommand
0xB083 WriteApuControlRegister
0xB08C AdvanceSoundSequenceStep
0xB08E AdvanceSoundSequenceStepAlt
0xB095 ReturnFromSoundCommand
0xB096 ReadLoopFlag
0xB09D HandleNoteTerminator
0xB0A9 HandleSustainCounter
0xB0B3 LoadSoundSequenceStep
0xB0C6 DecrementSustainCounter
1 0x8000 Reset
2 0x800E WaitForFirstPpuWarmup
3 0x8019 WaitForSecondPpuWarmup
4 0x802C InitializeGameState
5 0x8052 UpdateNametableFromStream
6 0x80AC StartStageSequence
7 0x8132 UpdateFadeFlashSprites
8 0x8164 InitializeRound
9 0x8226 MainGameLoop
10 0x82AC DivideBy20
11 0x82E1 SilenceSoundChannels1And3
12 0x82ED SilenceSoundChannel3
13 0x82FA StartIntroMusic
14 0x82FF StartMusicSequence
15 0x832A UpdateHudTallyTiles
16 0x8353 UpdateScoreTallyDigits
17 0x8372 WriteScoreTallyDigits
18 0x8399 LoadOamShadowFromTable
19 0x83A7 UpdateActorPositions
20 0x845C UpdateRoundTimers
21 0x856C PlayRoundChangeMusic
22 0x8596 UpdateScrollTarget
23 0x8598 UpdateScrollTargetAlt
24 0x8639 BeginVblankFrame
25 0x8644 ForceRenderingOff
26 0x8653 SetRenderingOffAndSignalVblank
27 0x865D UpdateRoundLogic
28 0x86CB NmiHandler
29 0x8751 FinishNmiUpdates
30 0x8759 FinishNmiRegisters
31 0x8764 IrqHandler
32 0x8765 SetSoundSequenceChannel1
33 0x877C RunGameStateDispatcher
34 0x87BF UpdateTimersAlt
35 0x87D1 PollControllerStart
36 0x87F2 UpdateFlashCounter
37 0x8837 PlayDeathSequence
38 0x8848 PollControllerBits
39 0x885B PollControllerAndAdvance
40 0x886C PollControllerBitsAlt
41 0x887C SignalVblankComplete
42 0x8882 PollControllerAndCheckRound
43 0x88A4 BuildOamList
44 0x88B0 EmitOamEntry
45 0x88EB EmitOamBlankEntry
46 0x88F9 EmitOamFromTable
47 0x88FF EmitOamAtOffset
48 0x8936 FindFreeOamSlot
49 0x8986 CompactOamList
50 0x89CE ReadControllerAndInit
51 0x8A28 UpdateStageActors
52 0x8A5E SpawnStageActors
53 0x8AE8 UpdateAttributeTable
54 0x8B07 ClearScrollLatch
55 0x8B20 WriteNametableTile
56 0x8B33 ClearAttributeRegion
57 0x8B92 RenderHudDigits
58 0x8BEF RenderHudDigitDescriptor
59 0x8BF8 SetRenderingOn
60 0x8C05 AdvanceAnimationSlot
61 0x8C0E QueueVramTransfer
62 0x8C11 QueueVramTransferAlt
63 0x8C65 ExecuteVramTransfer
64 0x8CB8 QueueVramDescriptor
65 0x934B UpdateActorX
66 0x9371 UpdateActorTile
67 0x9374 UpdateActorTileAlt
68 0x94B1 TestPlayerFacing
69 0x94C7 SelectActorSlot
70 0x94C8 SelectActorSlotAlt
71 0x94CF ClearActorPosition
72 0x94D7 ClearActorFields
73 0x94FE SetActorFlag
74 0x9585 UpdateActorState
75 0x95AE ComputeActorLeftEdge
76 0x95D2 ComputeActorRightEdge
77 0x95D9 CompareActorToLimit
78 0x9653 SetActorTileFromPalette
79 0x9667 StoreActorTile
80 0x969E AdvanceActorCounter
81 0x96AA SetActorTileValue
82 0x96C8 TestActorCollisionFlag
83 0x96F7 ComputeActorLeftEdgeAlt
84 0x9722 ComputeActorRightEdgeAlt
85 0x9729 TestActorRange
86 0x9732 MoveActorByStep
87 0x97BC SetActorAnimation63
88 0x97C0 SetActorAnimation62
89 0x97C4 SetActorAnimation61
90 0x97D5 UpdateActorAnimation
91 0x97FC TestAnimationSlot
92 0x982A ComputeActorLeftEdgeAt
93 0x9858 ComputeActorRightEdgeAt
94 0x985F TestActorValue
95 0x98E6 ComputeActorLeftEdgeAlt2
96 0x9911 UpdateActorTileFromIndex
97 0x9918 UpdateActorTileFromIndexAlt
98 0x99A3 SetSoundSequenceChannel2
99 0x99E2 LookupActorTileId
100 0x99E4 LookupActorTileIdAlt
101 0x9A24 StoreActorCoordinates
102 0x9A2E StoreActorCoordinatesAlt
103 0x9A3F FlipActorFacing
104 0x9A4E SpawnActorPair
105 0x9A6B SpawnActorPairAlt
106 0x9AE8 RunBytecodeInterpreter
107 0x9B05 DispatchThroughPointer
108 0x9B25 RunBytecodeProgram
109 0x9CBB ResetBytecodeState
110 0x9D4E CopyActorCoordinates
111 0x9DD6 SetActorBounds
112 0x9DE6 LoadActorTileIndex
113 0x9E6E UpdateActorFacing
114 0x9E7F QueueNametableWrite
115 0x9F14 FlushNametableWrite
116 0x9F4B UpdateActorMovement
117 0x9F54 MoveActorByOpcode
118 0x9FDF AddToNametableValue
119 0xA002 SubtractFromNametableValue
120 0xA01D ProcessBytecodeOpcode
121 0xA085 ComputeBytecodeOffset
122 0xA0A5 ResetBytecodeAccumulator
123 0xA0C6 ResetBytecodeAccumulatorAlt
124 0xA0F4 AdvanceBytecodeProgram
125 0xA0FB ResetBytecodeStateAlt
126 0xA12E ComputeActorTileRow
127 0xA15D ResetBytecodePointers
128 0xA17F RunActorScript
129 0xA250 LoadActorConfigByte
130 0xA253 ComputeActorConfigOffset
131 0xA25F CopyConfigBytes
132 0xA270 LoadActorConfigRecord
133 0xA298 CompareConfigValue
134 0xA2C8 ReturnFromConfigLoad
135 0xA2C9 LoadActorConfigFields
136 0xA316 StoreActorPaletteByte
137 0xA32C StoreActorControlByte
138 0xA338 ReadActorConfigByte
139 0xA435 CompareActorConfigValue
140 0xA46F StoreActorConfigResult
141 0xA482 LoadActorConfigAlt
142 0xA49F StoreActorConfigResultAlt
143 0xA4AA LoadActorFacingFlag
144 0xA4C7 LoadActorConfigBlock
145 0xA4DF TestSoundChannelState
146 0xA4F2 SetSoundSequenceChannel2Alt
147 0xA4FF FlipObjectIndex
148 0xA508 LookupObjectIndex
149 0xA512 UpdateObjectSlots
150 0xA51B AdvanceObjectSlot
151 0xA5DD WriteNametableBytes
152 0xA660 SetSoundSequenceChannel4
153 0xA68E UpdateObjectList
154 0xA695 UpdateObjectListEntry
155 0xA6F8 UpdateObjectCollisions
156 0xA79F CheckObjectScroll
157 0xA82F ClearScrollLatchAlt
158 0xA834 UpdateObjectSprite
159 0xA884 SetObjectFlags
160 0xA8D1 AdvanceObjectFrame
161 0xA8E9 TestObjectActive
162 0xA949 UpdateObjectAnimations
163 0xA952 UpdateObjectAnimation
164 0xA992 UpdateObjectTimer
165 0xA99E DecrementObjectCounter
166 0xA9AB UpdateObjectState
167 0xA9D8 ToggleScrollRegionFlip
168 0xA9FF BuildObjectList
169 0xAA56 ReadControllers
170 0xAA63 ReadControllerShiftRegister
171 0xAAB8 StoreControllerButtonBits
172 0xAACD EnableRendering
173 0xAADC DisableRenderingAndClearNametables
174 0xAAEF ClearNametablePattern
175 0xAB68 CopyPaletteFromTable
176 0xAB98 LoadShadowOamBlock
177 0xABA3 FillShadowOamBlock
178 0xABB1 UploadPalette
179 0xABE4 ApplyPpuScrollAndControl
180 0xABF7 ShiftAccumulatorRight4
181 0xABFC ClearOamShadow
182 0xAC0D SelectLevelVramWriter
183 0xAC3B SetLevelVramStream
184 0xAC43 FillAttributeBlock
185 0xAC4C WriteAttributeBlock
186 0xAC62 LoadAttributeStream
187 0xAC78 SetPpuAddressHighAnd40
188 0xAC82 WriteCompressedVramStream
189 0xAC88 AdvanceSourceByte
190 0xAC96 DecodeVramStreamToken
191 0xACA7 WriteVramRunToken
192 0xACC9 AdvanceDestinationWord
193 0xACD7 AdvanceCountWord
194 0xACE5 WithRenderingEnabled
195 0xACF5 WriteCompressedVramBlocks
196 0xAD19 ComputeCollisionBits
197 0xAD36 ComputePlayerOverlapBits
198 0xAE45 SetDefaultOverlapBits
199 0xAE9B ComputeEdgeOverlapBits
200 0xAF25 InitializeSoundEngine
201 0xAF3B UpdateSoundFrame
202 0xAF40 UpdateAllSoundChannels
203 0xAF43 UpdateSoundChannel
204 0xAF55 AdvanceSoundChannelNote
205 0xAF69 WriteSoundChannelState
206 0xAF6B RebuildApuEnableMask
207 0xAF73 StartSoundChannelNote
208 0xAFAB AdvanceSoundSequencePointer
209 0xAFC5 ComputeEnvelopeMask
210 0xAFD9 ComputeTonePeriod
211 0xB000 ShiftNoteTableIndex
212 0xB007 WriteToneRegisters
213 0xB029 WriteFixedToneRegisters
214 0xB03A ReadNoteDuration
215 0xB049 StoreNoteDuration
216 0xB059 StoreShortNoteDuration
217 0xB05D BuildApuChannelMask
218 0xB062 WriteApuEnable
219 0xB06C ApplySoundCommand
220 0xB083 WriteApuControlRegister
221 0xB08C AdvanceSoundSequenceStep
222 0xB08E AdvanceSoundSequenceStepAlt
223 0xB095 ReturnFromSoundCommand
224 0xB096 ReadLoopFlag
225 0xB09D HandleNoteTerminator
226 0xB0A9 HandleSustainCounter
227 0xB0B3 LoadSoundSequenceStep
228 0xB0C6 DecrementSustainCounter