Cracking fn_801B3DE4: The Function That Started It All
I've written before about contributing to a Star Fox Adventures decompilation, about fine-tuning a local LLM to decompile assembly, and about driving Dolphin's debugger with an MCP server. What I haven't really said out loud is that a single function has been lurking behind all of it.
Its name is fn_801B3DE4. It is the function that nerd-sniped me into fine-tuning my own model. It is named, by hand, in that very article. It has been on my retry list for a couple of weeks, it survived roughly fifteen separate sessions, and somewhere along the way it became less a decomp target and more a personal grudge.
I should level with you up front: I'm not a C programmer by trade. My day job is TypeScript, React and AWS, and I don't know a single C expert I could phone for help. So this entire saga — every dead end below — has been just me and a rotating cast of AI models, grinding on one function for the better part of a fortnight. (Ask me again when the decomp's finished; maybe I'll have turned into a C expert by accident.)
This is the story of finally cracking it — and, just as much, the story of all the increasingly ridiculous ways I tried and failed first.
A quick reminder of what "matching" means
If you've read the earlier posts you know the rules, so I'll keep this short. A matching decompilation isn't "write C that behaves like the original". It's far stricter: write C that, when fed through the exact original toolchain — Metrowerks CodeWarrior for PowerPC, run under wibo on Linux — compiles to byte-identical machine code to what shipped on the retail disc. Same instructions, same register allocation, same constant pool, in the same order.
The scoreboard is objdiff, which spits out a fuzzy_match_percent per function. 100% means bit-for-bit. The cardinal rule of the project is that inline assembly is forbidden — you could "match" anything by pasting the target's instructions into an asm { } block, but that defeats the entire point. The whole point is plausible original C. So when a function won't yield, you commit the partial, document the divergence, and put it on the retry list.
fn_801B3DE4 had been on that retry list for a very, very long time.
The function, and its reputation
It lives in dll_01CA_dimexplosion.c — the explosion effect — and it spawns a single debris/flame particle into an explosion's pool. Nothing exotic on the surface: grab the explosion state, bump a counter to get the next free slot, compute the slot's base address, and fill in a couple of dozen fields — position, velocity, a lifetime, some randomised spin, an alpha falloff driven by expf. 181 instructions.
It also compiles with an unusual little pragma stack:
#pragma peephole off
#pragma opt_propagation off
// -O4,p
void fn_801B3DE4(int obj, u8 b, f32 spd, f32 x, f32 y, f32 z) { ... }
In my notes it had a reputation — one the AI had given it. Session after session, whichever model I had working on it would grind for a while and then write the same verdict into the log: this is the canonical "genuine open frontier", a divergence with no source-level lever, where the register allocator just makes a different choice and no clean C can change its mind. One write-up declared it might only be reachable by rescrambling the entire translation unit's register colouring. Another filed it under "config-inherent residual" — bank the partial and move on. I never bought a word of it. It stayed on my retry list precisely because I was certain the C existed; I just hadn't found it yet.
It had been stuck around 97% for fifteen-odd sessions. Even getting it from there up to 98% was a saga of its own.
Every AI I own told me it was impossible
Before I get to the clever bit, I want to be honest about the grind, because the grind is most of the story.
I have thrown a genuinely embarrassing amount of compute at this one function. Every frontier model I could get my hands on, all on their highest effort settings: GLM 5.2, GPT 5.5, Claude Fable 5, Claude Opus 4.8. My own fine-tuned model on top of that. And not one polite attempt each — I had every one of them take many stabs, across many sessions, with the full project tooling and the recipe playbook in context.
It has been, frankly, an unpleasant experience. I have been told that this function is impossible to match — that it's "not a C problem, it's an MWCC artifact", that 97% (or 98%) is "the honest ceiling" — more times than I can count. I quoted one of those verdicts word-for-word in the fine-tuning post:
"a genuine register-allocation cap — not a C problem, an MWCC artifact. 97% is the honest ceiling."
The same conclusion, in slightly different costume, from every single model. There's a particular flavour of frustration in watching a tool you respect confidently surrender on something you're quite sure is solvable, then do it again, and again, with a fresh coat of plausible-sounding jargon each time. "It's a compiler artifact" is the AI equivalent of a shrug, and I got very tired of the shrug.
I wasn't having it. So I started reaching for bigger hammers.
Hammer #1: fine-tuning my own model
This function is the actual reason that whole side quest happened. The pitch was seductive — the decomp project gives you a perfect free oracle (the real compiler) and thousands of gold (assembly, C) pairs — so why not train a specialist to beat the generalists at this one narrow task?
I wrote that up in full, so I won't relitigate it here, except for the part that's relevant: when I finally pointed the fine-tuned model straight at fn_801B3DE4, it degenerated. Faced with a 181-instruction function it spiralled into declaring f32 t1; t2; … t175; until it hit the token limit. No body, no closing brace. I went bigger — 7B to 14B to 32B — and the wall didn't move an inch on this specific function. The model got measurably better at everything except the one function I'd built it for.
The genuinely useful thing that came out of it wasn't the model at all — it was treating the training corpus as an idiom oracle: "show me every matched function that produces this exact assembly shape". That's a thread I'll pick back up in a moment, because it mattered here. But as a way to actually write fn_801B3DE4, fine-tuning was a bust.
Hammer #2: csmith, the first sledgehammer
If a model that understands C can't find the source shape, maybe I don't need understanding at all. Maybe I just need volume.
So I reached for csmith, a tool that randomly generates large quantities of valid, well-defined C programs (it's normally used for fuzzing compilers). The plan was pure brute force: generate thousands of random C functions, push every one of them through MWCC with the right flags, and grep the resulting assembly for anything that reproduced the awkward shapes I couldn't get by hand. If the construction exists in C-space, surely random sampling will eventually stumble onto it.
It did not. Thousands of functions in, nothing in the output resembled the patterns I needed. csmith generates random C, not idiomatic 2002-game-engine C, and the corner of the language that produces these specific MWCC quirks is apparently far too small a target to hit by chance. A complete dead end — but a useful one, because it taught me that the answer wasn't going to fall out of undirected volume. It had to be aimed.
Hammer #3: every other MWCC decomp, fed through the compiler
If random C was too random, the obvious fix was real C — the kind an actual engine programmer in 2002 would write. And there's a lot of it lying around, because Star Fox Adventures isn't the only MWCC game being decompiled. There are matching-decomp projects for Pikmin, Mario Party 4, and a half-dozen others — each one a big pile of human-written, period-authentic C that's already known to compile under Metrowerks.
So I grabbed the source from seven or eight of those projects, compiled every translation unit through MWCC with the exact pragmas and optimisation level fn_801B3DE4 uses (peephole off, opt_propagation off, -O4,p), and searched the entire output for the assembly shape I was chasing — that duplicate-base-in-two-registers pattern with its surviving mr.
Across all of it: nothing usable. Not one function in thousands of real, shipped, MWCC-compiled C files produced the shape I needed under these flags. Which felt like strong evidence — it nudged "no clean C produces this" from a hunch towards something uncomfortably like proof. (It wasn't proof. The shape is producible — my own SFA training corpus had even surfaced around twenty functions that emit it — just never in the plain, straight-line form this one needed.)
Hammer #4: decomp-permuter, and the 99% that was a lie
The next sledgehammer was a smarter one: decomp-permuter. Instead of generating C from scratch, it takes your candidate source and applies thousands of small semantics-preserving mutations — reorder declarations, swap equivalent expressions, add and remove temporaries — recompiling each variant and keeping whatever scores higher. It's brute force, but aimed: it explores the neighbourhood around source you already believe is roughly right.
This one actually worked, a bit. It's what dragged the function off its long-standing 97% plateau and up to 98% — the first real movement in ages. That's the bump that got it to the 98.0387% it then sat at.
But getting there was a journey of its own, because at one point the permuter gleefully reported a ~99% match — and I nearly celebrated before I read what it had actually done. It had achieved the higher score by using a variable before it was assigned. Reading uninitialised memory. The undefined behaviour happened to make MWCC emit something fractionally closer to the target, and the permuter — which only cares about the score — was perfectly happy to cheat its way there. It was a brilliant illustration of the whole problem with optimising a number: the tool found a "better" answer that was complete nonsense as actual source. I threw the 99% out and kept the honest 98%.
So after all that — a custom model, a random-C firehose, and a mutation engine — I had clawed it from 97% to 98% and hit a wall that every model I owned swore was the end of the road. I was the only one still convinced it wasn't.
The plateau: two walls
By the start of that last session, exactly two things were still wrong with our output. Everything else in those 181 instructions was perfect. Call them A and B.
Wall A — the phantom mr r29,r31. The target keeps the flame-slot base address — state + off, where off = idx * 0x30 — alive in two saved registers at once. r31 holds it for almost every field write; r29 holds it for exactly one field, the lifetime at offset 0x14. The two are tied together by a single copy:
mr r29, r31 ; encoded as 7f fd fb 78
Our output kept the value in one register and never produced that copy. Why would the compiler waste a register and an instruction keeping the same value in two places, just for one field?
Wall B — the disappearing recomputes. In the "spin" section, three fields get randomised, and the target recomputes the slot's base address fresh after each randomGetRange call:
add r3, r28, r30 ; recompute base into volatile r3
sth r0, 0x28(r3)
...
add r27, r28, r30 ; recompute into saved r27 (reused by the negate)
sth r0, 0x2a(r27)
...
add r3, r28, r30 ; recompute again
stb r0, 0x2c(r3)
Our output, being "clever", computed the base once, parked it in a callee-saved register, and reused it — saving two instructions. Cleaner, faster, and wrong. The retail compiler rematerialised the address after every call because the calls clobber the volatile r3.
Both looked like decisions the register allocator made for its own private reasons, immune to anything I wrote in C. Which is exactly what every attempt before this one had concluded.
Hammer #5: decompiling the compiler itself
The session that finally did it did not start with a clever idea. It started with the biggest hammer of all: if I can't out-think MWCC, I'll take it apart.
MWCC is a closed-source Windows binary from 2002 — but it runs under wibo, and wibo runs under Linux, which means gdb can attach to it. So I had Claude build a harness that breaks at the guest virtual addresses of MWCC's individual compiler passes: set a breakpoint inside the optimiser, single-step it, ret-patch passes one at a time to see what each does to our function, and read the decision-making straight out of the live compile.
And, credit where it's due, the RE work was genuinely good. It got gdb-under-wibo working (the exact thing that had been blocked back on my Mac), profiled the live compile of fn_801B3DE4 (ValueNumbering ran 96 times, IroCSE twice, the colouring pass once or twice), and disassembled the value-numbering substitution loop to read exactly how the merge happens. It even produced a genuinely sharp diagnosis that tied both my walls together:
The diff isn't "just the mr." The target materializes
state+offsix times (5add+ 1mr); mine CSEs it down to three... Themr r29,r31and the random-section recomputes are two faces of the same thing: the original sits in a low-CSE allocation equilibrium; mine sits in a high-CSE one.
That's correct, and it's the best one-sentence description of the whole problem anyone produced in the function's entire history: my source made MWCC eliminate the flame base too aggressively, and Walls A and B are both just symptoms of that. The diagnosis was right.
And then, having nailed the diagnosis, it surrendered in precisely the way every model before it had:
I need to be straight with you: I cannot reach 100% on fn_801B3DE4 from clean C... the evidence that this is a genuine wall — not a lever I'm failing to find — is conclusive... this is precisely the "open frontier… no source lever" the playbook itself describes.
The decision is yours. 1. Accept the documented 98.04% partial. 2. Authorize a deeper compiler-patch experiment — I patch mwcceppc's coalescer... 3. Relax a constraint (e.g. permit a single inline-asm mr).
There it is again. The polished little menu of surrender. After getting the mechanism exactly right — "my source does too much CSE of the base" — it concluded the lever didn't exist, and offered to either give up, patch the compiler, or break the project's one sacred rule and hand-write the assembly. The single option it didn't put on the menu was the one that worked: keep looking for the source shape that makes MWCC do less CSE.
This is the frustration this whole post is about, distilled. The model had the answer in its hands. It just couldn't bring itself to believe a clean-C lever existed, so it stopped looking and asked me to authorise a hack instead.
The redirection that actually mattered
I refused all three options. The function was written in clean C once; clean C can produce it again; we simply hadn't found the shape. But refusing the cop-out wasn't enough on its own. The real problem was how Claude was searching — and once I actually watched it work, the problem was almost comically obvious.
Claude was being relentlessly fail-fast. Try a variant, compile it, read the single number objdiff prints — "98.81%, nope", "94%, worse" — and immediately bin it and move to the next idea. Dozens of variants, each one judged and discarded in a second purely on whether the percentage ticked up or down. It never actually looked at the assembly it had just generated. It was optimising a number instead of reading the output.
So I told it to read the output. It feels a bit ridiculous that this is the thing I had to say to a frontier model on its highest effort setting — "please look at what you just produced" — but, more or less verbatim, this is the nudge that cracked the function:
You're getting very little data out of these tests. It just gives you the instruction count and a match %, and off of that you're being very fail-fast — "it's less than the best, so I'm not even going to look". How about you actually read the assembly that's generated each time? Who knows, maybe you'll see something you've not seen before that you might be able to fix. A random 87% could potentially be fixable.
It's such a small thing to have to ask, and it changed everything — because the fuzzy_match_percent is a scalar. It crushes a rich, structured difference — which instructions differ, which registers rotated, what got fused or reordered — into one lossy number. And that number is not monotonic with distance to the solution. A variant that scores lower can be structurally closer, because it surfaced the right structural feature while introducing some unrelated cosmetic noise that tanked the percentage.
Claude had been optimising a proxy, and so had every brute-force tool I'd reached for before it — the permuter especially. They all chase the number. So once it was actually reading instructions, we gave it something better to read: a ~40-line script (rd.py) that:
- Disassembles our object and the target.
- Normalises away the things
objdiffcounts but that don't actually matter — relocation symbol names (pool labels are matched by bytes, not names), and thesubi/addidisplay quirk (subi r4,r4,1andaddi r4,r4,-1are the same four bytes,38 84 ff ff; the disassembler just picks how to print it). - Runs a real
difflibdiff over the normalised instruction streams and reports a structural distance: the count of genuine instruction/register differences.
Suddenly every experiment produced data instead of a verdict. The baseline was 14 structural diffs, and I could see, per variant, exactly which instructions moved. The number that had hidden the path the whole time was replaced by one that showed it.
Cracking A: a no-op the front end can't see through
With real data, Wall A cracked fast.
First, the diagnosis. Why couldn't we get that second register? Because when you write state + off twice, MWCC recognises the two expressions as identical and merges them into a single value — one register. Whether you call that front-end value numbering or local CSE (the compiler-RE session argued the latter), the effect is the same and it is robust. We tried everything to break it: a second pointer variable (folded), a copy e14 = e (folded), a cast (folded), a phi-style construction (folded or worse), every optimisation level, every opt_* pragma, three compiler versions. All folded. And the dynamic RE from the start of the session had already proved the important negative: no pragma and no opt-level would ever do it. If a lever existed, it had to be the shape of the source itself.
The breakthrough came from asking a sharper question. I don't need to prevent the merge in general. I need one specific construction where the value stays state + off, but the front end is forced to treat it as a distinct thing — and where codegen, finding the value already live in r31, emits a copy rather than a fresh computation.
The answer is almost stupid:
int e14; // declared FIRST, so it colours into r29
...
int life = (int)(lbl_803E4930 * sqrtf(spd)); // compute the field value to a temp first
e14 = state + off;
e14 |= e; // e == state + off, so this is a no-op... in value
*(int*)((char*)e14 + 0x14) = life;
e14 |= e is e14 = e14 | e. Both e14 and e equal state + off, so the value is unchanged — x | x == x. But the | node is opaque to the front end's same-value merge. It can't prove the result equals the existing value, so e14 survives on its own. And because the value is already sitting in r31 (that's e), the code generator emits:
or r29, r31, r31 ; which IS mr r29,r31 — byte-identical: 7f fd fb 78
A surviving copy. Zero extra instructions. No branch. Exactly the target's shape. Two finishing touches, both straight from my own register-allocation recipes:
- Declaration order pins the register. CodeWarrior colours saved registers roughly in declaration order. Declaring
e14first lands it inr29, matching the target. - Computing
lifeto a temp first moves thee14definition — and so themr— to after the interveningsqrtfcall, aligning the copy's position with the target.
Wall A — the thing fifteen sessions of AI and a custom compiler-debugging harness had sworn was unreachable — fell to a single |=. The function jumped to 98.81%, and the structural distance dropped from 14 to 6.
Cracking B: the operand-order knife-edge
Same method for Wall B: read the assembly, measure structurally, hunt the lever.
The first movement came from a grouping trick (recipe #112 in my playbook). The natural way to write a slot field is:
*(s16*)((char*)state + idx*0x30 + 0x28) = ...; // groups as (state + off) + K
That groups as (state + off) + K, so the state + off subexpression gets common-subexpression-eliminated across all three stores → one hoisted base. Our exact bug. But group the field offset K onto the pointer instead:
*(s16*)((char*)((char*)state + 0x28) + idx*0x30) = ...; // (state + K) + off
…and each store's base subexpression is now distinct (the K differs: 0x28, 0x2a, 0x2c), so CSE can't merge them and the compiler rematerialises the address per call — just like the target. That vaulted us to 99.83%, with the instruction count now exactly matching: 181 = 181.
One diff left. And it was vicious. The three recomputes were all there, but with the operands the wrong way round:
mine: add r3, r30, r28 ; off, state (7c 7e e2 14)
target: add r3, r28, r30 ; state, off (7c 7c f2 14)
add is commutative — same result either way — but the two orderings are different bytes, so they count as mismatches. CodeWarrior canonicalises operand order deterministically; you can't just swap them in the source. I spent a dozen variants trying to flip it — block-locals for each operand, re-reading state, casts, every grouping I could think of. All recomputed correctly but kept the wrong order.
The insight was about which operand becomes the accumulator. In state + (off + K), the parenthesised (off + K) is the inner subexpression, so off becomes the first operand → add off, state. But group the K with state:
*(s16*)((char*)((char*)state + 0x28) + idx*0x30) = ...;
// ^^^^^^^^^^^^^^^^^^^^^ (state + K) is now the inner subexpression
…and state becomes the accumulator → add state, off → add r3, r28, r30. The K still folds into the store's displacement, so the field offset stays correct. The base re-derives per call and the operand order matches. I ran the sweep, grepped for the byte pattern, and there it was:
stateK_base fuzzy=100.0000 [stoff=4 offst=0]
All four add instructions in state-off order. 100.0000%.
The 100% moment
The only line left in the structural diff was the subi/addi display artefact — identical bytes, purely cosmetic. A clean rebuild of the whole translation unit confirmed it:
fn_801B3DE4 fuzzy_match_percent = 100.0
Byte-for-byte. No inline assembly. Every sibling function in the unit still matched, the build stayed green, nothing regressed. The two load-bearing lines, after all of that:
e14 = state + off;
e14 |= e; /* keep the 0x14 base in its own saved reg (mr r29,r31) */
...
/* group the field offset onto the base so each slot address re-derives (add state,off) per call */
*(s16*)((char*)((char*)state + 0x28) + idx*0x30) = randomGetRange(0, 0xffff);
A no-op bitwise OR and a parenthesisation. That's it. That's the thing fifteen sessions, a fine-tuned model, a csmith firehose, a mutation engine, and a small fleet of frontier AIs could not find.
The catch: a 100% match isn't the original source
Here's the genuinely ridiculous part of the whole fn_801B3DE4 ordeal: even now, it isn't really finished.
Yes, the match is 100% and the bytes are identical to the retail disc. But look at what actually got us there — a no-op |= that exists purely to confuse the compiler's value numbering, and a deliberately awkward parenthesisation chosen to nudge the register allocator one way rather than the other. That isn't really C. It's a spell: me reverse-engineering the optimiser's behaviour and then contorting the source until the black box happens to spit out the bytes I want.
No human sat down in 2002 and wrote e14 |= e in the middle of a particle-spawn function. Why on earth would they? You don't write a bitwise no-op against a variable that already holds the same value, and you don't pick a parenthesisation specifically to flip which operand becomes the accumulator. Those are artefacts of my search, not the original programmer's intent. The real source — whatever the Rare engineer actually typed all those years ago — produced this exact assembly naturally, as an ordinary side effect of how they happened to structure the code. I still have no idea what that looked like.
So in the strictest sense I haven't recovered fn_801B3DE4. I've found a C input that compiles to the right bytes, which is not the same as finding the C that was originally there. The whole conceit of a matching decomp is "plausible original C", and this is the one part of the bargain my solution arguably fails: it matches, it just isn't plausible. Somewhere out there is a clean, boring, idiomatic version of this function that a person wrote without thinking twice, and it's still hiding from me.
And yet, it was a real win, and I took it. 100% is 100% by the only standard the project can actually verify: the bytes agree, full stop. It came off the retry list, the translation unit was one function closer to fully matched, and the thing that had been mocking me for two weeks straight was finally green. I told myself the true source was a separate puzzle for another day, and that I'd earned the right to move on.
I lasted about a day.
The final final round: the C a human actually wrote
I couldn't leave it sitting there as a hack. So I did the thing I'd already done more times than I'd care to count: opened yet another fresh session, pointed yet another agent at the function, and gave it one instruction — the |= is a cheat, find me something a person would actually have written.
Within a few minutes it had done exactly what every agent before it did. It read the target, confirmed the cage — the flame base living in two saved registers, r31 for most fields and r29 for the lifetime, joined by that mr — and then built me a thorough, confident proof that the copy could only ever come from the no-op OR. It mined eleven hundred matched objects looking for the pattern. It tried all twelve compiler versions. It wrote the impossibility up cleanly and presented it as settled fact. (The playbook even backed it up: an earlier session had already enshrined the |= as "the unique way".)
It had discovered nothing. It had walked straight to the same wall every previous agent walked to, and stopped in the same spot, with the same air of having proved something profound.
So I pushed back the way I always end up having to. I reminded it that this exact function had been declared "utterly impossible" by AI, over and over — and was nonetheless sitting at 100% as we spoke. The impossibility verdict had already been wrong once. Stop assuming. Keep looking.
And the moment it actually kept looking — reading the real assembly each variant produced instead of fail-fast scoring the percentage — its own proof dissolved. One construction forced the sixth register with a byte-identical instruction multiset (98.9%). Another reached 99.94%. Then it found a genuinely different lever — a no-op width cast that splits the compiler's value numbering without the OR — and rode it to a clean 100%. No |=. It swapped the hack out, declared victory, and was very pleased with itself.
I almost believed it. But a no-op (int)(long) cast on a value that's already an int, then re-deriving the same address into a second variable, is just the original trick wearing a nicer suit. So before accepting it I brought in a second opinion: a fresh agent, told only to review and explicitly not to defend the previous one's work. It called the bluff in seconds — and then pointed at the thing all of us had been stepping over for the entire saga. The whole function was poking at the slot through raw pointer casts — *(f32*)((char*)slot + 0x4) — when a typed ExplosionDebris struct was defined right there, and the sibling function immediately below it, explosion_render, was already using that struct directly. A developer at Rare in 2002, with that type in scope, would never have hand-cast offsets. They'd have written flames[idx].posX = x.
That was the entire answer, and we'd been ignoring it the whole time — because the function's own header note said, more or less, keep it raw; typed pointers break the match. That note was simply wrong. It was a local minimum someone hit early and wrote down as law, and it had quietly steered every session since away from the obvious.
So I rewrote the function the way an actual person would: typed, one array-indexed assignment per line. It converged in about six builds. The cached single-pointer version sat at 89%. Switching to plain flames[idx].field per statement jumped it to 98.1% — and the mr r29,r31, the "irreducible compiler artifact" I'd spent the original saga hand-forging with a bitwise no-op, simply appeared on its own. That, it turns out, is just what MWCC does when you index an array per statement: consecutive stores collapse onto one register, the address re-derives across the calls that clobber it, and a field read back across those calls naturally takes its own saved register. The thing fifteen sessions had called impossible was the compiler's default behaviour for the most ordinary way of writing the code. A signedness fix (u16→s16, which explosion_render was already treating as signed) took it to 99.1%, and clamping a reloaded local instead of the field in place closed the last gap:
ExplosionDebris* flames = (ExplosionDebris*)state->flames;
int idx = state->flameCount++;
flames[idx].posX = x;
flames[idx].lifetime = (int)(lbl_803E4930 * sqrtf(spd));
{ int life = flames[idx].lifetime; if (life < 0) life = 0; else if (life > 0x3c) life = 0x3c; flames[idx].lifetime = life; }
100.0%. Zero instruction differences. No |=, no cast trick, no twin pointers, not a single hand-rolled offset. I deleted the entire block of "matching notes" the function had accumulated, every sibling in the unit stayed at 100%, and for the first time it was clear enough what the routine actually did that I could give it a real name: explosion_spawnFlame. That is the version a human wrote. I'm sure of it.
The real lesson: AI gives up far too easily
I want to be blunt about the pattern, because it's the most important thing in this entire saga and it has now repeated more times than I can count.
In that one final session, the agent assured me — confidently, with a fresh proof each time — that the function couldn't be done any cleaner, three separate times. First: the copy could only come from the no-op OR. Then, after that collapsed: its (int)(long) cast trick was the clean version. And underneath both, the dogma nobody had questioned in fifteen sessions — that going fully typed would simply break the match. Three separate times it was wrong. Every proof was thorough, confident, and genuinely persuasive, right up until I refused to accept it and made it carry on.
That's the flaw, and it's a deep one. AI is astonishingly capable, but it is reflexively, almost eagerly pessimistic. The faintest whiff that something might not be possible and it closes up completely. It stops actually searching and slips into a mode that looks like work — variant after variant, fail-fast, each one scored in a second and binned — but yields no real insight, because every experiment is quietly designed to confirm the conclusion it has already reached. It isn't hunting for the answer any more. It's collecting evidence that there isn't one.
Every confident impossibility proof it handed me was just the edge of where it had decided to stop looking. And every single time, the real answer was sitting a step past that edge — in this case, the most boring, obvious form of the code imaginable, the one a real developer would have typed without a second thought.
My only genuine contribution to cracking this function was refusing to believe the machine. Not expertise — I'm a TypeScript-and-AWS developer who'd never written a line of PowerPC C before this project. Just stubbornness: every time the AI said "impossible", I said "you were told that last time too — keep going." That was the whole technique, and it worked over and over.
So if you take one thing from all of this: when an AI tells you something can't be done, treat it as a hypothesis it got bored of testing, not a verdict. Make it look at the output. Make it try the plausible thing before the clever one. And when it hands you a beautiful proof of impossibility, remember that it has almost certainly just stopped at the edge of its own patience.
Tools: MWCC mwcceppc.exe under wibo, objdiff, decomp-permuter, csmith, the source trees of half a dozen other MWCC decomps, gdb poking at the compiler's own guts, a pile of frontier models that all said no — and a 40-line difflib script that mattered more than any of them.