14 October 2026: how much of the ROM is code? And bank $C1 is done
Every progress figure on this site divides the code matched so far by the amount of code in the game. Until today that second number, 358,356 bytes, came from a one-off survey done at the start. Now that whole banks are matched, it could be checked, and it was wrong by a third.
What was counted as code
The biggest error is two banks, $CA and $CB, which the old survey credited with about 92 KB of code. They are the game's music. The sound driver keeps a table of 62 sample addresses and a table of 83 songs, and between them they cover all of $CA and nearly all of $CB. The old figure for $CA begins at the exact byte where the song data starts.
Bank $CC was the next largest: 27 KB claimed, about 4.4 KB actually code at the end of the bank. The rest is tables that the battle code reads. Ten more banks ($DE, $E4, $E5, $F5 to $F9, $FC, $FE) hold palettes, map tables, pointer tables and compressed data, and no jump or call from any matched or traced code leads into them.
Where the old figure went: $CA and $CB are song data, and most of $CC is tables. The totals are 358,356 and 239,779 bytes.
Two pieces of real code are now counted separately, because they don't run as 65816 code where they sit in the ROM: the sound chip's own program in $C7 (7 KB, for a different processor), and a 26 KB compressed block in $C3 that the game unpacks into RAM and runs from there.
How it is measured now
For each bank, the count is the matched code (exact), plus the unmatched code that can be reached by following jumps and calls from matched code and the interrupt vectors, plus an allowance for code that can only be reached through jump tables nobody has decoded yet. The tool that does this was tested by hiding the source of a bank that is already matched and seeing how close it got. For $C0 and $C1 it came within 2–4%. For $C2, which still has a lot of unmatched code mixed in, it came out 17.6% low.
That last result matters for how to read the new figure. The estimate leans low, and a smaller total makes the percentage bigger. So the honest headline comes with a range.
An independent check reproduced every number, confirmed the sound tables and the code-free banks from the ROM itself, and asked for exactly that caveat to be stated plainly.
Where the re-survey left things
The game has about 240 KB of 65816 code, not 358 KB. main stood at 170,895 matched code bytes: 71.27%, with a likely range of 65–73% and the true figure more likely below 71% than above it. All 2,403 matched routines were independently verified.
Nothing was matched or unmatched by this change. The work done is the same; the measure is better.
Bank $C1 is complete
The last 2,982 bytes of $C1 are matched, so every byte of the battle engine's bank now comes from source. Most of it is the 57 target handlers that enemy behaviour scripts call to pick who an action hits: the party, the enemies, the enemy itself, the nearest or farthest, the lowest HP, everyone with a given status, a fixed slot, or a random pick. The rest is the bank's entry block (the battle's entry point and its service dispatcher) and a stretch at the end of the bank that holds cut-off copies of code from an older build, never run.
The handlers have bugs of their own. Target $05 can loop forever in one state, $08 never compares the first party member's HP, $1F stores its loop counter from the wrong register, and $38 stops clearing the list after its second entry. Review also turned up something subtler. Several handlers leave out "self" by comparing against a stored battler slot. On the enemy's own turn that slot is the enemy. In the pass that runs every enemy's script in turn, though, nothing sets it, so "self" there is whoever acted last. It took six review rounds to describe exactly when that slot holds what.
The equipment screen and the menu's drawing code
In $C2, 4,904 bytes: the menu's pad input, cursor and sound requests, and the draw-list interpreter that builds windows, text and numbers (with the play-time clock and item names and descriptions). One of the menu modes turned out to be the equipment screen. A table of slot names sits right next to it in the ROM: "Weapon", "Helmet", "Armor", "Accessory".
A Mode 7 scene and the white fades
In $FD, 4,515 bytes: two palette animations that fade or pulse colours toward white, a complete Mode 7 scene started by event command $FF $80-$8F (an iris window, a sprite bitmap drawn with rays, palette cycling), and the routine that uploads the field's animated tiles. Which scene in the game it is has not been traced yet. Two of the white fades write only one byte of each colour, so they change one channel and leave the rest of the colour as it was.
The decompressor and the jet bike race
The first 4,299 bytes of $C3 are the bank's kernel. The centrepiece is the decompressor, called from 42 places in the game. Its header now describes the packed format completely, and two reviewers each wrote their own decoder from that description and ran it over the ROM's packs. Every one unpacks cleanly. The largest pack, 26 KB at $C3:1313, unpacks to 34 KB of 65816 code at $7E:3000. A block of staff text in the same bank reads "JET BIKE RACE", so that module is the race against Johnny. The rest of the kernel is the services that module calls: a script interpreter, task lists, sprite builders, a bitmap scaler, and the window shapes (circles, wipes and four-sided shapes) that the field also uses.
The packed format on toy data: a control byte says which items are literals and which are back-references, and a reference copies from earlier output, byte by byte.
The module itself is still raw bytes. Matching it would need a packer that rebuilds the exact compressed bytes, which is a project of its own.
The sound driver's 65816 side
Every sound and song request in the game goes through one entry point in $C7. That bank's 65816 code is now matched: 3,601 bytes that boot the sound chip, upload its program, and pass commands to it through four I/O ports. The handshake is written out step by step. The bank also holds a table of 83 songs and 62 instrument samples. The sound chip's own program, which runs on a different processor, is a separate job.
The rest of $FD
From $FD:A8A5 to the end of the bank, everything now comes from source: the fixed party and commands for one scripted battle, the animation lists for each location's tiles and palettes, and older copies of two routines that nothing runs. It is all data, so it doesn't move the code figure.
How a battle moves and draws
All of bank $CF's code is matched: 7,424 bytes. It is what runs every frame of a battle. One routine uploads graphics during the screen's blanking period, another steps everything else. Then come the movers that carry battlers and effects along lines, curves, arcs, circles and ellipses, or shake them in place. The sprite builder sorts every battler and effect by depth before it draws them, and the damage numbers bounce. When an enemy falls, a routine fades it out and takes it off the field.
Review found that two of these headers had their logic backwards. The objects that follow other objects are updated from the last one down, so a follower gets the old position of a lower-numbered leader but the new position of a higher one. The rule for hiding sprites at the screen edge doesn't hide them at all in one case: a sprite just off the left edge comes back partly visible. Both are now described as the code really behaves.
Setting up a battle, and the save files
The code at the end of $CC sets a battle up. Over several frames it uploads the battle graphics to video memory in stages, lays out the enemy names, and builds the list of who is fighting next to whom. It also has the helpers that reset an action's state between turns. Among them is a routine that returns before uploading its last two pieces, so the code that would upload them never runs.
From the end of bank $FF come the menu routines that $C2 calls. They build the tile sets for the menu font, step a wave effect, and draw a colour gradient. They also save and load the three save slots in battery-backed memory, with the checksum that guards each one. The check at boot that looks for valid save files writes its answer over the first byte of the play-time counter. The menu clears that counter right after, so nothing breaks.
The first part of $CD
$CD is the largest bank of code still unmatched, about 14 KB. Its first 5.8 KB are now matched. It is the battle's frame and screen work: the interrupt that runs once a frame and decides what to upload, the window wipe that opens and closes a battle, screen shake, the text boxes for battle messages and for the tech and enemy info panels, the routine that dissolves a defeated enemy pixel by pixel, and the scripted background effects on the third layer.
One header took five review rounds. The battle code's service dispatcher promises that every service hands the registers back as it found them, and that can only be checked for a service whose whole call tree is matched. Each round the matched part grew, and the list of which services still reach unmatched code went stale again. In the end the header lists, for each service, exactly which unmatched addresses it can reach. The reviewer checked that with a call-tree tracer, and it caught one path the first tracer had missed because it didn't follow branches into other routines.
Battle effects in $D1, and a lost palette upload
The code at the end of $D1 is matched: 6.4 KB of battle effects. It has the battle's own interrupt handlers, palette whitening, a background gradient that follows a battler's HP, window shapes (bands, boxes, waves, and four-cornered shapes drawn line by line), screen shake and bounce.
$CD and $D1 call each other constantly, so the two batches were matched side by side, each with stand-ins for the other. Merging them meant replacing those stand-ins with the real routines and settling about 70 RAM addresses that the two sides had named differently.
Review turned up a real bug in the game. At the end of a battle the code installs a second frame interrupt and waits for it. That interrupt writes the palette upload and the HDMA switch through the data bank the interrupted code was using, and while the battle-exit code waits, that bank is RAM. So in those frames the palette upload and the HDMA re-enable land in RAM instead of on the hardware. Only the writes that name the hardware bank directly reach it. It took two reviewers and three rounds to pin down exactly which writes get through.
Two of the window routines have a quieter side effect. Each 16-bit store to the WRAM data port also writes one byte to an address that mirrors a port of the sound chip.
The code in $CE
$CE holds a little under 4 KB of code among its scripts and tables, and all of it is matched. There is a routine that copies one line's window edges into the other window table, a wave that sways the second background layer, and a copier of 256 identical load-and-store pairs that the caller enters part-way in to copy exactly as many words as it needs. There is also an angle routine like the one in $C1, and a routine that fills a window with the half of the screen on one side of a line through the centre. A reviewer checked all 1,024 entries of the angle table it reads against the true angles: none is off by more than about 1.
The battle effect script
Battle effects are small scripts: a stream of bytes where each opcode moves something, waits, branches, draws a window shape or sets up a palette cycle. The second part of $CD is the table of 122 opcodes and 94 of the routines behind them, plus the helpers the whole bank leans on: divide and multiply, sine products and a 3D rotation for spinning models, frame waits, and the queue that feeds graphics to video memory a piece at a time.
The opcodes have their own oddities. One moves the script pointer a byte short, so the high byte of its address operand is read as the next opcode. Another reads its points from past the end of the calculation results. Opcodes $FA-$FE have no table entry at all, so a script that used one would jump into whatever code follows the table.
The item screen, and using items
Back in $C2, 84 more menu routines are matched: the item list with its sorting and scrolling, the equipment screen's character list and the stat comparison, and what happens when an item is used from the menu. Healing items roll their amounts, cap them at 999 and float the numbers up over the character; the item that heals a random amount picks from a small odds table; the stat-raising items stop at each stat's cap.
A few small oddities turned up. The routine that writes HDMA line counts splits anything over 127 lines into two entries, except 255, which wraps round and ends the table instead. Nothing ever asks for that many lines, so it never shows. The table of equipment labels has "Attack:" and "Defense:", but the accessories' entry points at bytes that are not text at all; whether that label is ever drawn for an accessory is not yet known.
The rest of $CD: windows and triangles
The last 4.3 KB of $CD are the remaining effect script opcodes, and with them the whole bank is matched. Most of them set the PPU's window and colour-math registers directly: which layers a window masks, how two windows combine, what gets added to or subtracted from the screen. The rest draw shapes into the window tables line by line: circles, domes, hanging and tall shapes built from one set of half widths, and filled triangles whose edges are stepped down the screen with a slope worked out by division.
The edge cases are where the oddities live. One opcode scrolls the screen to line up with an enemy, but subtracts in 8 bits and pads the result with zeros, so for an enemy high on the screen it adds a large positive number instead of a negative one. Whether that shows depends on how far the layer wraps. The shape copies decide how much to trim from the bottom before allowing for what they already trimmed from the top, so a large shape near the top of the screen can lose lines it never needed to lose. And the routine that copies a whole window table, given a large enough offset, runs past the end of one table into the next.
Techs and settings
Two more menu screens in $C2 are matched. Mode 3 is the tech screen: a window of party members on the left, and on the right three pages of techs, probably single, double and triple, each with its MP cost. Two accessories cut that cost to a half and a quarter. Techs that can be used outside battle borrow the item screen's healing code, taking their strength from the caster's stats. Mode 4 is the settings screen, including the editor that maps actions to buttons and checks two of them are not given the same button. With them come the threads that type out description text a little at a time, and the 23 small records the menu uses to send graphics to video memory.
Most of what the reviewers found here were header claims about register states, but a few real oddities are now written down. The tech screen draws as many blank member rows as it drew filled ones, rather than filling up to three. The button editor's clash check has a table of seven pointers for eight lists, so the eighth reads two bytes from near the start of the bank as an address. And the settings text clears five tiles past the end of each row.
The shop, naming and loading
Three more menu modes in $C2. The shop lists what can be bought and sold, with a count window and a side view of how each party member's stats would change with the item. Its prices come from each item's price word: selling gives half of it rounded up, and buying scales it by a factor kept in RAM, rounded up and kept between 1 and 65,000. Gold is capped at 9,999,999. Name entry is the character grid, with a check that turns a name away if another character already has it. The title's load screen picks a save slot; holding Select and Start on the second controller over a slot asks to erase it, and one flag in save RAM enables a second choice that loads part of a slot before starting a new game, probably New Game+.
The shop's count of how many of an item the party holds has a small slip: when it finds a slot with the item but a count of 0, it reloads the register it was comparing with and goes on looking for item 0 instead.
Scene helpers in $C6, and the end of $FF
The cutscene scripts in $C2 can call out to native code with one opcode, and the code they call lives in $C6: 3.6 KB of it, more than the survey had guessed. Little of it is called directly from other code, so finding most of the callers meant unpacking the eight compressed script packs and searching them for that opcode. What it does is the visual side of scenes: window shapes (ellipses, capsules, wipes), colour gradients, a wave on one background layer, unpacking graphics and palettes, nudging objects to random places, and a Mode 7 view with perspective, recomputed line by line through four HDMA channels.
The last 921 bytes of $FF turned out not to be code at all, or at least not code that runs: they are fragments of an older build of the routines just before them, left in the ROM, with nothing pointing at them. The same check showed that the few hundred "unmatched code" bytes the survey had left in $FD and $FF are really data. So outside $C2, every byte of game code is now matched.
That also means the progress figure below has stopped meaning quite what it says. The 713 bytes it still counts as unmatched are exactly those data bytes in $FD and $FF, and $C2 already has more code matched than the survey credited it with, so the menu modes still left there do not show in the figure at all. The survey is due to be redone.
The rest of $C2: party order and saving
The last 4.4 KB of $C2 are matched, so the bank now builds entirely from source. Modes 5 and 7 turn out to be the same screen, the one for reordering the party and swapping members with the reserve: mode 5 is reached from the menu, and mode 7 probably from events. Mode 6 is the save screen, with its slot windows showing play time, a place name and the party's portraits, and a set of flags that make it refuse to save. The bank also holds the shop's arithmetic: a 24-by-16-bit division, a multiply, and adding, paying and capping gold.
The party screen has a small slip. When it greys out characters who are not allowed to move, its test lets an empty row through and then uses the row number as a party position, so it can dim the wrong character's palette, or, if that position is empty too, halve fifteen words of unrelated memory. And like $FF, the end of $C2 holds 500 bytes of leftovers from earlier builds: tails of the shop code at older addresses, and older code still that no longer matches anything in the bank.
Redoing the survey, and what 100% means
The survey from the start of the day was redone against the current source. Every bank's code figure now equals its matched code, 249,251 bytes, so the figure reads 100.00%. That number needs reading carefully. Once every jump and call out of matched code lands in matched code, the survey's own method cannot find anything more, so it returns the matched total by construction: the same method, tried on $C2 before it was matched, missed a fifth of that bank. The evidence that nothing is left lies in separate checks, made by an independent review. Of the 12,661 distinct exits of matched routines, none lands on an unmatched byte. The compressed module in $C3 and the cutscene scripts were unpacked and every call they make into the ROM lands on a matched routine. All 73 entries of the pack table were unpacked and, apart from the module, hold graphics, tile maps and other data. And a search of every unmatched byte for anything shaped like a routine found nothing longer than 19 instructions, all of it noise in tables and graphics.
So the claim is: all reachable 65816 code in the ROM is matched, verified and readable. It does not include the SPC700 sound driver (7,153 bytes, a different processor) or the code inside the compressed $C3 module (counted apart), and about 4.7 KB of leftovers from older builds are matched as data, since nothing runs them.
The sound driver's other processor
The SNES plays sound on a separate chip with its own processor, the SPC700, and Chrono Trigger's driver for it sits in $C7. At boot the main CPU copies 7 KB of it into the sound chip's memory and starts it there. A trace from its entry point splits that upload into about 6.5 KB of code and two small blocks of data: the tables it dispatches through, a pitch table, and one short built-in sample.
The driver's way in: Audio_Boot sends the block at $C7:24C3 through the four ports to audio RAM $0200, and every later request uses the same ports.
asar can assemble SPC700 code too, so this is matched the same way as the rest: labels take the sound chip's addresses while the bytes land at their places in the ROM. The project's tools only understood the 65816, so the driver got its own disassembler, its own table of routines, a lint for its headers and a separate progress figure, kept apart from the 65816 one. The first 1 KB is matched and verified: the start-up, the main loop that writes the sound chip's registers every few milliseconds, and the code that takes commands from the main CPU and starts songs and sound effects. Reading that side confirmed a few things the 65816 code only implied: a request for the song already playing is answered with $FF, one command sends the sound chip back to its boot ROM, and two commands set the current song aside so a later request brings it back with a fade-in.
Where that leaves things
main now matches 249,251 bytes of 65816 code: all of the reachable 65816 code in the ROM, with the SPC700 driver and the compressed module counted apart. All 3,736 matched routines are independently verified.
Next
The SPC700 driver stands at 2,364 of 6,543 code bytes (36.13%), all of it verified: the second batch added the main CPU's volume, tempo, stop, mute and pause commands and the code that receives song data, which quietly drops any block aimed at the driver's own memory. The track pass is next, and after the driver the code inside the compressed $C3 module.