15 October 2026: how the sound driver reads a score
The third batch of the SPC700 sound driver is the part that plays the music: the pass that walks through every voice's track many times a second. With it the driver stood at two thirds of its 6,543 code bytes. The fourth batch, later the same day, took it the rest of the way: the whole driver is now built from source, and every one of its 177 routines and tables is verified.
The music format
A track is a stream of bytes. Anything below $A8 is a note, and one byte carries two things at once: divided by 14, it gives the step above the track's base note, and the remainder picks a length from a table of 14 durations. The next fourteen values are ties, the fourteen after those are rests, and everything from $C4 up is a command, dispatched through a table by pushing the handler's address and returning to it. Command $EB ends a track.
How the driver reads a track byte. A note byte holds two numbers: divided by 14 it is the step, the remainder picks the length.
The pitch of a note comes from a table of semitones, scaled by the tuning of the instrument's sample and shifted by octave. The tuning works in two ranges: a byte below $80 raises the pitch by up to half again, and a byte from $80 up lowers it by up to half, which is how one sample can serve instruments recorded at different pitches. A note is released two passes before its time runs out, unless the driver looks ahead and finds a tie, and each song carries a small drum kit: twelve entries of sample, note and pan for the voices that play percussion.
Slides, vibrato and a one-sided tremolo
On top of the notes the driver runs slides of volume, pan and pitch, a pan that sways from side to side, vibrato, and tremolo. The reviews found the tremolo less tidy than it first looked. A phase below $80 raises the volume and a phase from $80 up lowers it, but the cap that should stop the result passing $7F only applies to some of the ways it can overflow; in the others the volume just wraps to its low byte.
The look-ahead that checks for a tie has a slip of its own. Most commands it steps over move the read position past their operands, but one, $DC, does not, so its operand is then read as if it were the next note.
The last part: the score's commands
The fourth batch is everything a score can ask for besides notes. Commands $C4 to $FF set volume, pan, tempo and echo, with a fade for each, and turn vibrato, tremolo, the swaying pan, noise, pitch modulation and echo on and off per voice. Others change octave, transposition, fine tuning, the instrument and its envelope. Each track has four loop slots, and jump addresses are relative to where the song was loaded, so the same song data works wherever it lands in the sound chip's memory.
Two commands let the game steer the music while it plays. Command $FA jumps only when a condition matches a value the game sets with a separate command, which is how a song can change course in response to what happens on screen. Command $F9 puts a marker where the game can read it back, so the game can follow where the song has got to. The driver can also put a song aside and bring it back: it copies the song's state to the top of the sound chip's memory, and on the way back fades the restored song in over sixteen steps.
The reviews of this batch found one more quirk of the original in the look-ahead. When it meets the branch command it compares the operand as a whole byte, while the command itself only looks at its low four bits, so a branch written with a larger operand can take the jump when played but is never followed by the look-ahead.
With this batch the separate file of not-yet-matched bytes is gone, and the length word in front of the driver, which tells the upload code how many bytes to send, is now worked out by the assembler from the source itself.
The packed module in $C3
The last code outside the source is a module the game keeps compressed: the jet-bike race, 26,393 bytes in the ROM that unpack to about 34 KB. To match it in its unpacked form, the build has to pack it again and get exactly the bytes the cartridge holds, so the first job was a packer. The format is a common one from the time, and after some experiment the original's choices turned out to follow a well-known compression program of 1989, with three small differences. That rule rebuilds all 60 compressed blocks in the bank byte for byte, without any table of special cases.
The module is now assembled from its own source, packed by that tool and placed back in the ROM, and its first 21 routines (the start-up, the task loops and the audio calls) are matched and verified. An independent audit of the new tooling tried hard to make the build count ROM bytes as source and found several ways in, each now closed and tested, with a check of the files the assembler opens as the final guard. The same day the project's own CI got the ROM: every change on GitHub is now rebuilt and compared byte for byte, not only on the machine that made it.
The packed module both ways: the game unpacks 26,393 bytes into 34,175 at $7E:3000; the build assembles that code, packs it, and must get the cartridge's 26,393 bytes back.
A second batch followed the same evening. The module's trace now follows the race's tasks and scripts all the way through: the tasks programs hand to the task list, the scripts each of the two script pages runs, the places where one script redirects the other, and the calls the code patches into itself at run time. With that, the module's code is a real count, 21,004 bytes, where before it was a lower bound of 7,052. Another 48 routines are matched: the arithmetic (multiplication, division, sine and a square root), the fades, the mode-7 table builder that rewrites its own code, the task helpers, and the number and sprite builders. Three review rounds sent headers back, mostly for callers that only a script reaches; the check that a header names every one of its callers now counts those too.
The rest of the ROM: a map and a plan
Beside the code, about 93% of the ROM is data, still copied from the original when the ROM is built. A survey now maps every one of those 3.9 million bytes: what each region holds, its format, and which matched routine reads it, with 99% of the bytes tied to a reader in the code. More than half of the ROM turned out to be 1,515 compressed blocks (map graphics, sprites, map layouts and event scripts), and every one of them repacks exactly with the packer made for the $C3 module. An independent audit rebuilt the list of blocks on its own and found a handful of mislabelled regions, all corrected before the survey went in.
The survey also settled what "every byte from source" can mean here. The game's graphics, music and text belong to its owners and will not be stored in this project; instead a tool reads them out of each person's own copy of the game, and the build works from those files. The plan that follows from the survey puts the end of copying any region of the ROM a few working sessions away, and a readable form for all of it (text, scripts, graphics) further off.
No more copying the cartridge
Until today the build started from a copy of the original ROM and assembled the source over it, so everything the source did not cover simply came along from the copy. That is gone. A tool now reads the player's own ROM once, checks it is the right one, and writes every surveyed piece of data to files that stay on the player's machine; the build packs the 1,515 compressed blocks again from those files, then assembles the source and the data onto a blank image, twice (once filled with $00, once with $FF) to prove that every byte of the 4 MB is written by something. The result is identical to the cartridge.
The bytes that come from those files are counted on their own lines and never as source: 1.47 MB placed as extracted pieces and 2.35 MB rebuilt by the packer, beside the code. Another independent audit tried to slip ROM bytes into the "from source" figures through the new files (links to the ROM, swapped files, a tampered survey, data written over code) and found no way in; the two small faults it did find, in the build's cache and in a check for data written over code with identical bytes, were fixed before the merge.
The build without a copy of the ROM, and tools/verify.py's count of where every byte comes from on main today.
The race, and the game's words
The third batch of the $C3 module is the jet-bike race itself: the step that runs every frame, with the bikes' speeds and caps, the boost on the B button, the rival's steering, the bumps when the bikes meet, the engine note sent straight to the sound chip, and the laps (eight to win). When the race ends, it hands the game a result and a prize: some scores, like 777 or 1234, count as special. The module now stands at 30% of its code. The race step's header took four review rounds, all over the same kind of detail: what exactly each comparison accepts. The 65816's signed tests look at one bit of a difference, so "$1000 or more" can quietly mean "$1000 to $8FFF", and one test even lets a bike count as touching the other when it is exactly the threshold away on one side but not the other. The final version states every comparison as the code branches on it.
What CMP then BMI really tests: the top bit of the difference. "$1000 or more" accepts only $1000-$8FFF, and the stars keep sums $00-$7E and $FF.
The game's text is now readable too. About 200 KB of it — every message, the item, technique and enemy names, the menus and the credits — comes out of the player's own ROM as plain text files, and an encoder turns them back into exactly the original bytes. The original compresses its text with a dictionary of 127 common fragments, and the surprise is that a simple rule reproduces every choice it made: at each point, take the longest fragment that fits, except for three fragments no string ever uses. So the text can be stored spelled out in full, with nothing to record about how it was compressed. An independent audit re-derived that rule on its own and checked it against all 11,455 runs of compressed text.
Text bytes, and the greedy rule on a toy dictionary: at each point take the longest entry that fits. The game's text follows this rule exactly, apart from three unused entries.
Tables, and what counts as known
The next part of the data is the game's tables: stats, growth curves, tech costs, cursor and sprite layouts, palettes. The first attempt turned 112 KB of them into readable records, and an audit took most of that back. A record only says something true if the code that reads the table fixes both its layout and how many entries it has; many of the tables had been laid out over a length that was only guessed (up to the next named table), and a few claimed more entries than the reading code could ever reach, in one case 190 where the index stops at 28. The rule now is that a table counts as structured only when its reader proves both, through a mask, a loop's end, an opcode range or a one-byte index; everything else stays as an unread block, with the reason written down. That leaves 101 tables and palettes, 15.9 KB, as readable records. It is a smaller number, and each one is backed by the code that reads it.
Pictures
The gallery now has real frames from the rebuilt ROM: the title screen, the Millennial Fair, the fight with Gato and Crono's room on the first morning. They come from one scripted run from power-on, and the same run on the original cartridge gives the same picture in all 15,574 frames.
The race on screen
The fourth batch of the $C3 module is how the race is drawn: the projection that turns a bike's place on the track into a position and size on screen, with a small cache of divisors for the perspective, the bikes, the rival and the panel of figures, the markers that show who is ahead, and the jobs that scale pictures and send their tiles during the screen's blank. Two of the race's smaller scenes came with it: the zoom out over the plane with its field of stars, and the mode-7 map with its rocks, slides and wave. That brings the module to 49% of its code, 213 routines, every one verified.
Its last review rounds were spent on a single routine, the one that moves the stars, and they show how much one carry flag can hide. The code adds a star's speed to its depth without clearing the carry first, so whatever the carry happens to hold leaks into the sum. Inside the task list that carry is always clear, but the same routine is also reached by a jump from elsewhere, where it is always set; and when a star's depth is exactly $7F with the carry set, the divider that sizes it divides by zero. The header now names all three ways that can happen, each one checked against the code.
Where that leaves things
The 65816 side is unchanged: all of the reachable 65816 code in the ROM is matched and verified. The SPC700 driver is complete: 6,543 of 6,543 code bytes (100%) and all 608 bytes of its tables, every routine verified by independent review. The packed $C3 module is at 10,305 of its 21,017 code bytes (49.03%), all 213 of its matched routines verified. The game's text, 202,895 bytes, round-trips from readable files.
Next
The rest of the $C3 module, and the next step of the data plan: the script languages, starting with the event scripts that drive every location.