the bytes don't remember the names

Here is a line from the Wave Race 64 source:

if (1) {} // FAKE: gives the original's i * 19 (shifts, not a multu by a hoisted 19)

It does nothing. Nobody at Nintendo wrote it. It’s there because without it, a 1996 compiler multiplies by 19 one way, and with it the compiler does it the other way, the way the cartridge does. There are 121 lines marked // FAKE in the tree now, and each one is there because the bytes asked for it.

Where it stands

In the last post I didn’t try to grow the game’s four-rider arrays from the inside, because Wave Race 64 was “only partly decompiled.” That clause is a lot less true now.

wr64-decomp turns the shipped binary back into C that compiles, with Nintendo’s own compiler, to the exact same ROM. Every build ends with a SHA-1 check against the cartridge. It’s a fork of LLONSIT/Wave-Race-64, whose two and a half years of groundwork made it possible. In three days it went from 951 to 1,253 of 1,348 functions, 70.6% to 93.0%, and from 39% to 68% of the code bytes. Nintendo’s OS library is done, audio is 226 of 228, and the courses and menus are 146 of 151.

So the bytes are nearly settled. The bytes were never the interesting part.

Gavagai

In Word and Object, Quine imagines a linguist dropped among speakers of a language with no shared history. A rabbit runs past and a speaker says “gavagai.” The linguist writes down rabbit. Quine’s point is that undetached rabbit parts or a stage in a rabbit’s life fit every piece of evidence just as well. Any number of translation manuals can agree with everything the speakers ever do and still disagree with each other. The evidence settles less than it seems to.

A matching decomp is that experiment with one witness, and the witness is a hash. It’s strict: one wrong instruction fails the build. But it answers one question only, does this compile to those bytes?, and a huge number of different C programs say yes. The hash picks out a family of sources. It doesn’t pick out the one somebody typed.

Working inside that family, every line of the C turns out to be one of three kinds.

Forced. Some of the original survives in the bytes. IDO gives each local its stack slot in the order it was declared, so a spilled variable’s offset tells you how many locals came before it in the original. Stores come out in source-line order, so three assignments sometimes match only on one line, which is a fair hint that someone in Kyoto wrote them on one line. That part is archaeology.

Free. Most of what makes code readable leaves no mark at all. Names, comments, struct fields, which file a function lives in. The compiler throws all of it away, so the hash accepts func_801F4120 and DrawLapRankSpeed equally.

Fake. Then there’s text that is wrong on purpose. if (1) {}. A temporary that exists only to move a value into the right register. Stack padding. None of it is the original. All of it is there so the bytes come out as they did in 1996.

Where translation bites

The fork’s rule for names is the field linguist’s discipline: a name needs evidence. That means a call into a function that’s already named, a game-state constant, an OS call or a string. And the evidence still runs out. One function is called Popup_UpdatePauseCursor, and the project’s own notes say it puts its cursor on the main menu’s rows, so the name may be wrong. Rabbit or rabbit parts.

Quine went further than this case allows. He thought there was no fact of the matter at all. Here there was one: somebody at Nintendo typed a name in 1996. The bytes didn’t keep it, and nothing we can measure will bring it back.

Pierre Menard, IDO edition

Borges has a story about Pierre Menard, a 20th-century writer who sets out to produce Don Quixote, word for word, without copying it. The narrator insists Menard’s text is a different and richer book than Cervantes’s, though not a letter differs, because of who wrote it and when.

This fork is written with Claude, and upstream doesn’t accept AI-generated code, so it’s independent and nothing from it goes back. I’d normally call identical bytes identical. Borges is the reminder that people care, reasonably, about who wrote a thing as well as what it says, and the upstream rule is that kind of care. The hash can’t see authorship. That doesn’t make authorship nothing.

What’s left

95 functions. They’re 7% of the count and about a third of the code, because the small ones went first, and the biggest is the race HUD at about 4,900 instructions. After that comes the half of the job that the hash can’t judge: names, structs and the documentation you need before you can find every place the game assumes four riders.

The hash will tell me when the bytes are right. Nothing is going to tell me when the names are.