- 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.
10 KiB
10 KiB
| 1 | # MUSCLE (U) routine purpose notes - Pass 2 | ||
|---|---|---|---|
| 2 | # addr<TAB>purpose | ||
| 3 | 0x8000 | Power-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. | |
| 4 | 0x800E | Spin on $2002 until bit 7 is set, so the write to $2000 just before it has taken effect. | |
| 5 | 0x8019 | Second vblank wait, this time before touching $4017/$4010. | |
| 6 | 0x802C | Reset 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. | |
| 7 | 0x8052 | Decode 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. | |
| 8 | 0x80AC | Stage 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. | |
| 9 | 0x8132 | Push or drop the two flash sprites (palette tiles $82) depending on PlayerCount and the fade state. | |
| 10 | 0x8164 | Round 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. | |
| 11 | 0x8226 | Main 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. | |
| 12 | 0x82AC | Restoring 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. | |
| 13 | 0x82E1 | Point sound channels 1 and 3 at the silent sequence at $B1A1/$B1B0 with step 0. | |
| 14 | 0x82ED | Point sound channel 3 at $B1B0 with step 0 (the tail of SilenceSoundChannels1And3). | |
| 15 | 0x82FA | Set the $14-frame transition timer and start the intro music sequence for all four channels. | |
| 16 | 0x82FF | Start a four-channel music sequence from the $B2B9/$B3B2 tables. | |
| 17 | 0x832A | Draw the two score/tally markers at $20BB and $24A6 when their flags at $0338/$0339 are set. | |
| 18 | 0x8353 | Redraw the score row at $20EC and $210C with the three current digits. | |
| 19 | 0x8372 | Write one score digit: blank the cell unless it matches PlayerCount, then stamp the tile. | |
| 20 | 0x8399 | Copy the 32-byte OAM shadow at $9EF4 into $0300 and upload it. | |
| 21 | 0x83A7 | Advance both actors: add the per-actor scroll delta to the position in $0340-$0342, then step the round counter and redraw the tally markers. | |
| 22 | 0x845C | Per-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. | |
| 23 | 0x856C | Start the short round-change jingle and wait for the NMI handshake. | |
| 24 | 0x8596 | Compare the actor position in $0340-$0342 against the screen bounds in $0328-$032A and latch the tighter limit. | |
| 25 | 0x8639 | Clear the vblank handshake ($0321, $0320) and re-enable rendering. | |
| 26 | 0x8644 | Force rendering off: PPUCTRL=$10, PPUMASK=0, updating both shadow bytes. | |
| 27 | 0x8653 | Force rendering off and immediately signal the main loop by setting $0321. | |
| 28 | 0x865D | Positive 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. | |
| 29 | 0x8751 | Finish 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. | |
| 30 | 0x8759 | NMI epilogue: poll $2002, update the sound engine, restore Y/X/A, and RTI at $8764. | |
| 31 | 0x8765 | Point sound channel 1 at the $B1EB sequence. | |
| 32 | 0x877C | Main game-state dispatcher. Counts down the transition timer at zero page $8D and runs one of fourteen subsystem updates chosen by that counter. | |
| 33 | 0x87F2 | Advance the screen-flash counter against the $2C-frame limit. | |
| 34 | 0x8837 | Play the player-death sequence: load the palette, upload the sound command, clear the attribute area, and respawn the actors. | |
| 35 | 0x8848 | Test the start/select bits of the raw controller byte. | |
| 36 | 0x885B | Read the controllers and advance the frame counter. | |
| 37 | 0x887C | Signal the main loop that the NMI frame is finished. | |
| 38 | 0x8882 | Read the controllers and test the round-transition flag at $0326. | |
| 39 | 0x88A4 | Build the OAM list for the active player: blank every slot, then emit one sprite per visible actor row. | |
| 40 | 0x88B0 | Emit a single OAM entry built from the actor record. | |
| 41 | 0x8936 | Find the next free OAM slot by scanning the $032E source list for a value matching the running sprite index. | |
| 42 | 0x8986 | Compact the OAM list, dropping entries whose actor has moved off screen. | |
| 43 | 0x89CE | Read the controllers, reset the per-player bookkeeping, and initialise the object list. | |
| 44 | 0x8A28 | Update the stage actors: pick the animation for each player from the palette index pair and hand off to SpawnStageActors. | |
| 45 | 0x8AE8 | Rewrite the attribute table entry at $209C through the compressed stream writer. | |
| 46 | 0x8B20 | Write one tile into the nametable at the address in zero page $10/$11 plus the per-round offset. | |
| 47 | 0x8B92 | Render the HUD digits: set up the palette copy, clear the digit cells, then run each digit descriptor. | |
| 48 | 0x8BF8 | Re-enable rendering: PPUCTRL=$10 with PPUMASK left at zero and the shadow updated. | |
| 49 | 0x8C05 | Advance the animation slot below the current one. | |
| 50 | 0x8C0E | Queue a VRAM descriptor built from the PlayerCount-relative index. | |
| 51 | 0x8C65 | Execute the queued VRAM transfer: copy bytes from (BytecodeStreamPointerLo/Hi) through the destination pointer and the two block counters. | |
| 52 | 0x8CB8 | Push a VRAM descriptor and bump the queue index. | |
| 53 | 0x9AE8 | Bytecode 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. | |
| 54 | 0x9B05 | jmp ($001C) - the handler table and the $9F2B table both land here to re-enter the interpreter at the next token. | |
| 55 | 0x9B25 | Main bytecode program loop: advance the program counter, dispatch the token, and resume. | |
| 56 | 0x9CBB | Reset the per-invocation script state at $034D/$034E and the flags in zero page $4D/$4E. | |
| 57 | 0x9E7F | Queue a nametable write described by a two-byte descriptor and advance the write pointer. | |
| 58 | 0x9F4B | Update actor movement from the object script, dispatching through jmp ($001C). | |
| 59 | 0x9F54 | Move an actor by one opcode: read the displacement, apply it, and redraw. | |
| 60 | 0xA01D | Process one bytecode opcode against the actor record, advancing the script pointer. | |
| 61 | 0xA17F | Run an actor script: set up the source/destination pointers, then walk the descriptor stream. | |
| 62 | 0xA250 | Read one byte of the actor configuration block, scaled by four. | |
| 63 | 0xA253 | Compute the byte offset of an actor configuration record. | |
| 64 | 0xA4FF | Flip the low bit of A, turning a player index into its partner (0<->1, 2<->3). | |
| 65 | 0xA508 | Resolve an object index through the per-player tables at $2A and $26. | |
| 66 | 0xA512 | Walk the object slots looking for one whose countdown at $45 is non-zero. | |
| 67 | 0xA68E | Update the whole object list, then the second half of it. | |
| 68 | 0xA6F8 | Update object collisions and the object scroll test. | |
| 69 | 0xA8D1 | Advance an object animation frame from the table at ($75),y. | |
| 70 | 0xA949 | Update both players' object animations. | |
| 71 | 0xAA56 | Read 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. | |
| 72 | 0xAA63 | Shift one controller port's eight bits in from $4016/4017 and run the edge-detection post-processing. | |
| 73 | 0xAACD | Enable rendering: PPUMASK=$18, PPUCTRL=$90, both shadows updated. | |
| 74 | 0xAADC | Disable rendering, then clear both nametables to tile $30. | |
| 75 | 0xAAEF | Set the PPU address from A and write $C0/$40 bytes of tile $30. | |
| 76 | 0xAB68 | Load the HUD palette and the per-round object configuration into the $0300 shadow. | |
| 77 | 0xABB1 | Upload the 32-byte palette from $0300 to $3F00, then restore PPUMASK. | |
| 78 | 0xABE4 | Write the latched scroll (PpuScrollX/PpuScrollY) and PpuCtrlShadow to the PPU. | |
| 79 | 0xABFC | Clear the 256-byte OAM shadow at $0200 to $F0. | |
| 80 | 0xAC0D | Select and run the level's VRAM writer from the three-entry table at $AC35, indexed by the level byte at $0326. | |
| 81 | 0xAC82 | Decode 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. | |
| 82 | 0xAC88 | Advance the compressed-stream source pointer (SourcePointerLo/Hi) by one byte. | |
| 83 | 0xACF5 | Write 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. | |
| 84 | 0xAD19 | Compute the collision bit flags for the active player and store them in zero page $15. | |
| 85 | 0xAF25 | Silence the APU: $4015=0, the enable mask cleared, and all four channels marked finished. | |
| 86 | 0xAF3B | Sound frame update: walk all four channels and update the APU. | |
| 87 | 0xAF40 | Update all four sound channels in turn. | |
| 88 | 0xAF43 | Update one sound channel: restart it from its sequence when finished, otherwise count the note timer down and step the sequence. | |
| 89 | 0xAFAB | Advance a sound channel's sequence pointer by step*2 from its base. | |
| 90 | 0xAFC5 | Compute the envelope mask for a channel from its envelope mode. | |
| 91 | 0xAFD9 | Compute the tone period for a channel from its note table and write the period registers. | |
| 92 | 0xB03A | Read a note's duration from the sequence and store it, doubling it for looped notes. | |
| 93 | 0xB06C | Apply one byte of a sound sequence: below $A0 is ignored, $A0-$AF patches the $4000 control byte, and $B0+ handles note and sustain data. | |
| 94 | 0xB0C6 | Decrement the channel's sustain counter and advance the sequence.0x8644 | Set 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. |
| 95 | 0x8653 | Call 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. |