Files
disasm/_wip/MUSCLE (U)/copilot/routine_purposes.tsv
T
tmerritt 4c644d2a20 Pass 3 for NES PowerPad Test Card (U): name all data tables and VramWrite streams
- Decoded the VramWrite ($E1CC) stream format: <addr hi> <addr lo> <count> <bytes>, $00 terminator
- Named all 9 payload streams ($E4CC-$E7C2, no gaps/overlaps) plus VramWriteSourcePointerTable at $F000
- Split PadBitTableLives into Player1TileTable ($E4B4) and Player2TileTable ($E4C0), 12 entries each (Y=0..11)
- Decoded CHR font (digits $30-$39, letters $3A-$52, space $5F; 'W' missing from the A-Z run)
  and recovered on-screen text including 'PRESS A TO TEST SIDE-A' / 'SIDE-B'
- Documented that the 16 KiB PRG is two byte-identical 8 KiB halves; $C000 copy is dead
- Documented 7 unreached subroutines inside the live code region (kept as .byte per Pass-1 policy)
- Fixed a stray duplicate sentence and stale _wip paths in root OVERVIEW.md

Byte-identical ROM verified.
2026-10-16 20:24:23 -04:00

10 KiB

1# MUSCLE (U) routine purpose notes - Pass 2
2# addr<TAB>purpose
30x8000Power-on entry: stack set up, PPU blanked through two vblank waits, APU frame counter silenced, $0300-$032C cleared, then the initial nametable stream is decoded into $0400 and the first stage is started.
40x800ESpin on $2002 until bit 7 is set, so the write to $2000 just before it has taken effect.
50x8019Second vblank wait, this time before touching $4017/$4010.
60x802CReset the game-state bytes at $0326/$032B/$032C and zero page $FF, clear $0300-$032C, then decode the compressed nametable pointed at by SourcePointerLo/Hi.
70x8052Decode the compressed nametable stream at (SourcePointerLo/Hi) into (DestinationPointerLo/Hi). A byte with bit 7 clear is a run of (byte & $7F) copies of the next stream byte; a byte with bit 7 set is a literal, written only when it differs from what is already at the destination. $0346 is set whenever VRAM actually changed.
80x80ACStage start-up: reset the animation and actor counters, clear the OAM shadow, initialise the sound engine, upload the HUD palette row, and hand control to MainGameLoop.
90x8132Push or drop the two flash sprites (palette tiles $82) depending on PlayerCount and the fade state.
100x8164Round initialisation: clear the shadow arrays at zero page $00-$BF, reset the controllers and the OAM shadow, collect the $81/$82 type sprites, set the per-player palette and sprite bases, and start the actors.
110x8226Main game loop. Waits for the NMI handler to raise $0321, services the two frame timers, reads the controllers, and restarts the stage or updates actor positions. $0321/$0320 form the vblank handshake with the NMI.
120x82ACRestoring binary division of the 8-bit value in A by the constant 20 ($14). Eight steps shift the divisor right, conditionally subtract it from the 16-bit remainder, and roll the quotient bit into $0349. Quotient -> $0349, remainder -> $0347/$0348.
130x82E1Point sound channels 1 and 3 at the silent sequence at $B1A1/$B1B0 with step 0.
140x82EDPoint sound channel 3 at $B1B0 with step 0 (the tail of SilenceSoundChannels1And3).
150x82FASet the $14-frame transition timer and start the intro music sequence for all four channels.
160x82FFStart a four-channel music sequence from the $B2B9/$B3B2 tables.
170x832ADraw the two score/tally markers at $20BB and $24A6 when their flags at $0338/$0339 are set.
180x8353Redraw the score row at $20EC and $210C with the three current digits.
190x8372Write one score digit: blank the cell unless it matches PlayerCount, then stamp the tile.
200x8399Copy the 32-byte OAM shadow at $9EF4 into $0300 and upload it.
210x83A7Advance both actors: add the per-actor scroll delta to the position in $0340-$0342, then step the round counter and redraw the tally markers.
220x845CPer-round timer and state update: redraw the score, start the round-change music, and either loop again or advance the round and prepare the transition objects.
230x856CStart the short round-change jingle and wait for the NMI handshake.
240x8596Compare the actor position in $0340-$0342 against the screen bounds in $0328-$032A and latch the tighter limit.
250x8639Clear the vblank handshake ($0321, $0320) and re-enable rendering.
260x8644Force rendering off: PPUCTRL=$10, PPUMASK=0, updating both shadow bytes.
270x8653Force rendering off and immediately signal the main loop by setting $0321.
280x865DPositive game-state branch of the NMI: update the flash counters and actors, detect a button edge, and bump the frame-skip counter in zero page $FE.
290x8751Finish the NMI: clear the vblank latch unless $0321 says work is outstanding, poll $2002 to release the PPU address latch, and run the sound frame update.
300x8759NMI epilogue: poll $2002, update the sound engine, restore Y/X/A, and RTI at $8764.
310x8765Point sound channel 1 at the $B1EB sequence.
320x877CMain game-state dispatcher. Counts down the transition timer at zero page $8D and runs one of fourteen subsystem updates chosen by that counter.
330x87F2Advance the screen-flash counter against the $2C-frame limit.
340x8837Play the player-death sequence: load the palette, upload the sound command, clear the attribute area, and respawn the actors.
350x8848Test the start/select bits of the raw controller byte.
360x885BRead the controllers and advance the frame counter.
370x887CSignal the main loop that the NMI frame is finished.
380x8882Read the controllers and test the round-transition flag at $0326.
390x88A4Build the OAM list for the active player: blank every slot, then emit one sprite per visible actor row.
400x88B0Emit a single OAM entry built from the actor record.
410x8936Find the next free OAM slot by scanning the $032E source list for a value matching the running sprite index.
420x8986Compact the OAM list, dropping entries whose actor has moved off screen.
430x89CERead the controllers, reset the per-player bookkeeping, and initialise the object list.
440x8A28Update the stage actors: pick the animation for each player from the palette index pair and hand off to SpawnStageActors.
450x8AE8Rewrite the attribute table entry at $209C through the compressed stream writer.
460x8B20Write one tile into the nametable at the address in zero page $10/$11 plus the per-round offset.
470x8B92Render the HUD digits: set up the palette copy, clear the digit cells, then run each digit descriptor.
480x8BF8Re-enable rendering: PPUCTRL=$10 with PPUMASK left at zero and the shadow updated.
490x8C05Advance the animation slot below the current one.
500x8C0EQueue a VRAM descriptor built from the PlayerCount-relative index.
510x8C65Execute the queued VRAM transfer: copy bytes from (BytecodeStreamPointerLo/Hi) through the destination pointer and the two block counters.
520x8CB8Push a VRAM descriptor and bump the queue index.
530x9AE8Bytecode interpreter entry. Reads a token byte through (BytecodeStreamPointerLo/Hi); tokens $C8-$EF are converted to an index and dispatched through the handler table at $9237 into jmp ($001C); anything else is an operand consumed by the current handler.
540x9B05jmp ($001C) - the handler table and the $9F2B table both land here to re-enter the interpreter at the next token.
550x9B25Main bytecode program loop: advance the program counter, dispatch the token, and resume.
560x9CBBReset the per-invocation script state at $034D/$034E and the flags in zero page $4D/$4E.
570x9E7FQueue a nametable write described by a two-byte descriptor and advance the write pointer.
580x9F4BUpdate actor movement from the object script, dispatching through jmp ($001C).
590x9F54Move an actor by one opcode: read the displacement, apply it, and redraw.
600xA01DProcess one bytecode opcode against the actor record, advancing the script pointer.
610xA17FRun an actor script: set up the source/destination pointers, then walk the descriptor stream.
620xA250Read one byte of the actor configuration block, scaled by four.
630xA253Compute the byte offset of an actor configuration record.
640xA4FFFlip the low bit of A, turning a player index into its partner (0<->1, 2<->3).
650xA508Resolve an object index through the per-player tables at $2A and $26.
660xA512Walk the object slots looking for one whose countdown at $45 is non-zero.
670xA68EUpdate the whole object list, then the second half of it.
680xA6F8Update object collisions and the object scroll test.
690xA8D1Advance an object animation frame from the table at ($75),y.
700xA949Update both players' object animations.
710xAA56Read both controller ports: strobe $4016, shift eight bits per port into zero page $FA/$FB, save the previous byte in $F4/$F5, and derive the face/direction masks in $F6-$F9.
720xAA63Shift one controller port's eight bits in from $4016/4017 and run the edge-detection post-processing.
730xAACDEnable rendering: PPUMASK=$18, PPUCTRL=$90, both shadows updated.
740xAADCDisable rendering, then clear both nametables to tile $30.
750xAAEFSet the PPU address from A and write $C0/$40 bytes of tile $30.
760xAB68Load the HUD palette and the per-round object configuration into the $0300 shadow.
770xABB1Upload the 32-byte palette from $0300 to $3F00, then restore PPUMASK.
780xABE4Write the latched scroll (PpuScrollX/PpuScrollY) and PpuCtrlShadow to the PPU.
790xABFCClear the 256-byte OAM shadow at $0200 to $F0.
800xAC0DSelect and run the level's VRAM writer from the three-entry table at $AC35, indexed by the level byte at $0326.
810xAC82Decode one compressed VRAM token: below $B8 is a literal, $B8-$C3 is a run whose fill value comes from the table at $ACBD and whose length is the next stream byte, $C0 additionally skips a byte first, and $C4 or above is a literal.
820xAC88Advance the compressed-stream source pointer (SourcePointerLo/Hi) by one byte.
830xACF5Write a block of compressed data to VRAM: the first byte is the block count, then each block supplies a 16-bit PPU address followed by a token run.
840xAD19Compute the collision bit flags for the active player and store them in zero page $15.
850xAF25Silence the APU: $4015=0, the enable mask cleared, and all four channels marked finished.
860xAF3BSound frame update: walk all four channels and update the APU.
870xAF40Update all four sound channels in turn.
880xAF43Update one sound channel: restart it from its sequence when finished, otherwise count the note timer down and step the sequence.
890xAFABAdvance a sound channel's sequence pointer by step*2 from its base.
900xAFC5Compute the envelope mask for a channel from its envelope mode.
910xAFD9Compute the tone period for a channel from its note table and write the period registers.
920xB03ARead a note's duration from the sequence and store it, doubling it for looped notes.
930xB06CApply one byte of a sound sequence: below $A0 is ignored, $A0-$AF patches the $4000 control byte, and $B0+ handles note and sustain data.
940xB0C6Decrement the channel's sustain counter and advance the sequence.0x8644Set PPUCTRL to $10 and PPUMASK to 0, updating both shadow bytes. Provably unreachable: no word in the image points at it except the jsr at $8654, and $8653 itself is unreferenced. Kept as code because the shape is unambiguous.
950x8653Call ForceRenderingOff, signal the main loop through SignalVblankComplete, then clear the vblank latch at $0320. Provably unreachable (no word in the image points at $8653). Kept as code because the shape is unambiguous.