docs: subagent workflow + port-learnings cheat-sheet

Distilled from Chris Judnich's 'Decompiling a Nintendo 64 Game in 84 Days'
(saved under docs/reference/): explicit per-task budgets exposed to the
agent, a shared append-only learnings file, file-partitioned parallelism,
cheap pre-flight passes, and a standard investigation-subagent brief
template. PORT-LEARNINGS.md is a one-screen index of the recurring
N64->PC bug classes into PCPortResearch.md §F; AGENTS.md points to both.
This commit is contained in:
James Dansereau committed 2026-08-29 09:52:47 -04:00
1 parent 3a02ded079
commit 64698f6b23
5 files changed
+719

No files matched your search

+3
View File
@@ -136,3 +136,6 @@ ge007.crash.log
# Claude Code local state / agent worktrees
.claude/
# saved reference webpage assets (keep the .htm, not the sidecar cruft)
docs/reference/*_files/
+1
View File
@@ -6,6 +6,7 @@
- Active work: **PC port** modelled on the Perfect Dark PC port (same Rare "Indy" engine family).
- **Read `docs/PCPortResearch.md` first** — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D33 in §11; current handoff + plan in §H).
- **Starting a fresh session?** Read `docs/HANDOFF.md` — the paste-ready brief with the immediate task, standing procedure, and environment gotchas.
- **Dispatching subagents?** `docs/AGENT-WORKFLOW.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/PORT-LEARNINGS.md` (recurring bug classes) first and appends to it.
## Non-negotiables
+131
View File
@@ -0,0 +1,131 @@
# Agent / subagent workflow for this port
Guidance distilled from Chris Lewis's *"Decompiling a Nintendo 64 Game
in 84 Days"* (`docs/reference/`, Snowboard Kids, 2026) — an AI-assisted
N64 decomp run 7× faster than its predecessor — adapted to this project,
which is a **PC port on top of a decomp**, not a byte-match decomp. The
mechanics differ (we have no permuter / m2c loop; our inner loop is
root-cause → narrow `#ifdef PORT` fix → visual verify) but the
project-structure lessons transfer directly.
## 1. Every subagent task gets an explicit budget, stated in the brief
Their key finding: an open-ended tool (the permuter) that "runs until
100% match or manually stopped" wrecks throughput; giving the agent a
**deadline it can see** let it trade tool-time against other
problem-solving and judge when to give up on a hard function.
Our open-ended tools are the same shape: rebuild-and-run-the-game cycles,
gdb, env-gated probe iteration, staring at PPM dumps. So every subagent
brief must state:
- a **wall/iteration budget** — e.g. "≤ N build→run→inspect cycles" or
"~M minutes of investigation"; and
- the **fallback on expiry**: *stop, revert probes, write up what you
have with a confidence rating* — a good write-up of a half-solved bug
is a deliverable, not a failure.
Escalate the budget as the project hardens: early bugs are cheap
(one-line truncations, off-by-ones), so cap tight; once those are gone
the survivors are structural (matrix handedness, format specs) and
deserve more room. Revisit the default each session.
## 2. A shared, append-only learnings file that briefs point every agent at
They had agents record generalisable IDO quirks in
`DECOMPILATION_LEARNINGS.md`; later agents read it and got better. "Agents
helped document IDO, and the documentation made subsequent agents better."
Our equivalent already exists but is diffuse: `docs/PCPortResearch.md` §F
is 200 KB of narrative. The **recurring PC-port quirk classes** should be
distilled into a short cheat-sheet every subagent brief links and every
subagent is told to append to:
- pointer-width struct growth (32→64) in ROM-serialized / pun-allocated
structs — the dominant bug class (D53.2, D56, D79, D98–D102)
- 16-byte PC `Gfx`/`Vtx` vs 8-byte N64 — reservation & copy sizing
(D50.6, D58, D85, D95)
- big-endian rodata read on LE: `f32` word-pairs (D73, D112), header
offset tables (D68, D87), packed bitfields (D78, D83)
- N64 hardware idioms fast3d doesn't emulate (fill-rect Z clear → D105;
LOD/detail tiles → D107; K0 segment address folds → D58, D84)
- offline sidecar converter preferred over runtime fixup for whole
formats (D43, D69, D88)
This is `docs/PORT-LEARNINGS.md` — one screen, an index into §F, not a
replacement. Every investigation-subagent brief links it; every subagent
is told to append any new generalisable quirk before reporting.
## 3. Parallel work: partition by file, consolidate often
They ran 4 git worktrees sharded by a hash of the candidate list, and
found the cost was **synchronisation** — a match in one worktree stayed
invisible to the others until merged, and merging all four took >1 h.
Their fix: make the similarity search read *all* worktrees so a new
reference propagates immediately, and merge to main periodically anyway.
For us (parallel investigation subagents, not worktrees yet):
- **Partition tracks by the files they will touch** so patches don't
collide. This session: Track 1 = `bg.c`, Track 2 = `chr.c`/`model*.c`,
Track 3 = `gunfire.c` + a new audit doc — disjoint, merged clean.
A brief that can't be file-partitioned shouldn't be parallelised.
- Subagents must **revert their own probes** before reporting (leave only
committed, env-gated, capped probes) and must **not** touch another
track's uncommitted edits — call them out in the report instead.
- The integrator (main session) consolidates to `master` per-track with
the §F write-up, then the next round of agents starts from a clean
tree. Don't let investigation branches drift.
- If we move to real worktrees: `data/` must be **copied, never
junctioned** (see the `data-dir-junction-hazard` memory —
`git worktree remove --force` follows the junction and deletes the real
baserom + sidecars).
## 4. Cheap automated pass before spending agent tokens
They scripted `m2c` across every unmatched function first (0.93 % hit
rate, but near-free) and told agents to exhaust known SDK
versions / compiler flags / `#if` paths before writing original code.
Our pre-flight before dispatching an investigation agent:
- `./build-pc.sh` green + `/linkcheck` clean
- run the existing env-gated probes for the area (`GE_D96`, `GE_D104`,
`GE_D69*`, …) and attach the output to the brief
- `GE_PCDUMP` + `tools_pc/pixcount.py` baseline capture
- grep `docs/PCPortResearch.md` §F for a prior instance of the bug class
- check `PD_PORT_CHECKOUT` for the PD analogue
(already a non-negotiable) — the brief should name the file to look at
Give the agent the results, not just the task. "Give the agent the
findings, not just the problem" is the through-line of the whole post.
## 5. What does *not* transfer
- **Model choice.** Their (unscientific) take was Codex > Claude for
reproducing IDO codegen. Our task is behavioural correctness, not
compiler-output matching — not comparable; ignore.
- **The permuter / match-percentage loop.** We don't bit-match. Our
"done" bar is: no fault + visually correct vs. N64 footage
(`docs/reference/n64-footage-*`), measured with `pixcount.py`.
- **"Understanding the function is the easy part."** True for decomp,
inverted here — our hard part is often *what N64 hardware/ABI behaviour
the C is silently relying on*.
## Standard investigation-subagent brief template
```
Repo: REPO_ROOT (git, master, Windows; PowerShell + Bash).
READ FIRST: docs/HANDOFF.md (top), docs/PORT-LEARNINGS.md,
docs/PCPortResearch.md §F <specific Dxx entries>, AGENTS.md non-negotiables.
TASK: <one bug, one subsystem>.
FILES YOU MAY TOUCH: <disjoint from other running agents>.
KNOWN-GOOD / RULED OUT: <list — do not re-investigate>.
PRE-FLIGHT ATTACHED: <probe output, pixcount baseline, pd_port pointer>.
BUDGET: <N build→run cycles / ~M min>. On expiry: revert probes, write up with confidence.
CONSTRAINTS: no game-logic changes; ABI/layout/format only, #ifdef PORT, documented in §F.
revert your own temp probes; do not touch other agents' uncommitted edits.
REPORT: (a) root cause + file:line evidence (b) fix diff or why-not
(c) probes left in tree (d) confidence. Append any generalisable
quirk to docs/PORT-LEARNINGS.md.
```
+74
View File
@@ -0,0 +1,74 @@
# PORT-LEARNINGS — recurring N64→PC bug classes
One-screen index into `docs/PCPortResearch.md` §F. **Every investigation
subagent reads this first and appends any new generalisable quirk.**
Details, evidence, and the fix for each instance live in §F under the
cited Dxx label.
## A. Pointer-width struct growth (32→64) — the dominant class
A decomp struct with pointer fields, or one pun-allocated into a fixed
N64-sized hole / hardcoded byte count, is **larger on x86-64**. Reading it
from ROM bytes misaligns; allocating it N64-sized overruns adjacent
state.
- Symptom: garbage pointer deref, corrupted neighbour field, delayed
fault far from the cause.
- Fix: store embedded ROM addresses as `u32`, cast at use site (PD ground
truth); or give real inline storage / `sizeof()`-based alloc under
`#ifdef PORT`.
- Instances: D53.2 (ModelSlot pun), D56 (watch Model raw offsets), D57
(rwdata record count), D67 (image_entry), D79 (bg_room_data), D98
(struct player alloc), D100 (player.model inline Model), D101/D102
(ModelNode*/weapon Model puns), D115 (gunfire THROW* raw offsets).
- **Open landmine:** raw hardcoded-offset accessors into `struct player`
/ `struct hand` — see `docs/AUDIT-M6-player-offsets.md`.
## B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64
Any buffer reservation, `memcpy` size, slot stride, or pool budget
expressed in N64 `Gfx`/`Vtx` units is **half-size** on PC.
- Instances: D50.6 (texCopyGdls copied only w0 of each 16-byte slot),
D58 (DL reserve 0x100→0x200), D85 (`bgWidenRoomGdl` 8→16 + bswap),
D95 (2× master-DL buffer + raised mempool ceiling).
## C. Big-endian rodata / ROM data read on little-endian PC
ROM assets and compiled-in `.rodata` are big-endian. Anything not run
through a converter or a runtime bswap fixup reads scrambled.
- `f32` values are BE **word pairs** — a naive byte reversal off-by-one
corrupts every float: D73 (sinf/cosf `du` pairs → `DVAL()` macro),
D112 (`d43_emit.py put_f32` `src[doff:doff+4][::-1]`).
- Header offset tables / pointers: D54 (cseq ALMidiHdr), D68
(Globalimagetable), D87 (ramromfilestructure), D88 (Usetup* tables).
- Packed bitfields cross byte boundaries differently: D78 / D83
(StandTile id/room, header mid/tail).
- **Rule:** prefer an offline sidecar converter (D43, D69, D88 pattern,
`tools_pc/d*_emit.py`) over a runtime fixup for a whole format.
## D. N64 hardware idioms fast3d does not emulate
- Z buffer cleared by pointing the colour image at it + fill-rect → does
nothing in fast3d; must emit `G_CLEAR_DEPTH_EXT` (D105).
- LOD / detail mip tiles: fast3d fabricates a crop when detail textures
are off → force base tile (D107).
- K0 segment-address folds (`OS_K0_TO_PHYSICAL | 0x80000000`) and
hand-inlined `BG_SEG_TO_PTR` need the `(u32)` 32-bit wrap the macro has
(D58, D84).
- Portal near-plane: z==0 clip points project to ±1e20; x86-64 float
garbage can come back `min>max` / non-finite and slip past a
degenerate-box check that clamps to full-screen on N64 (D106).
## E. Process / method notes
- Investigation loop is: reproduce → env-gated capped probe → root-cause
→ narrow `#ifdef PORT` fix → visual verify (`GE_PCDUMP` +
`tools_pc/pixcount.py` vs `docs/reference/n64-footage-*`).
- gdb **launch** mode is too slow for timing-dependent faults; gdb
**attach** to an already-running process is fine and fast.
- Check `PD_PORT_CHECKOUT` for the PD analogue before
writing anything new — same Rare engine family.
- Don't re-investigate a closed §F finding or re-derive a format spec
that already has a converter.
@@ -0,0 +1,510 @@
<!DOCTYPE html>
<html lang="en-au" data-darkreader-mode="dynamic" data-darkreader-scheme="dark"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><style class="darkreader darkreader--fallback" media="screen"></style><style class="darkreader darkreader--user-agent" media="screen">@layer {
html {
background-color: var(--darkreader-background-ffffff, #181a1b) !important;
}
html {
color-scheme: dark !important;
}
iframe {
color-scheme: dark !important;
}
html, body {
background-color: var(--darkreader-background-ffffff, #181a1b);
}
html, body {
border-color: var(--darkreader-border-4c4c4c, #736b5e);
color: var(--darkreader-text-000000, #e8e6e3);
}
a {
color: var(--darkreader-text-0040ff, #3391ff);
}
table {
border-color: var(--darkreader-border-808080, #545b5e);
}
mark {
color: var(--darkreader-text-000000, #e8e6e3);
}
::placeholder {
color: var(--darkreader-text-a9a9a9, #b2aba1);
}
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
background-color: var(--darkreader-background-faffbd, #404400) !important;
color: var(--darkreader-text-000000, #e8e6e3) !important;
}
* {
scrollbar-color: var(--darkreader-background-b0b0b0, #454a4d) var(--darkreader-background-f1f1f1, #202324);
}
::selection {
background-color: var(--darkreader-background-0060d4, #004daa) !important;
color: var(--darkreader-text-ffffff, #e8e6e3) !important;
}
::-moz-selection {
background-color: var(--darkreader-background-0060d4, #004daa) !important;
color: var(--darkreader-text-ffffff, #e8e6e3) !important;
}
}</style><style class="darkreader darkreader--text" media="screen"></style><style class="darkreader darkreader--invert" media="screen">.jfk-bubble.gtx-bubble, .captcheck_answer_label > input + img, span#closed_text > img[src^="https://www.gstatic.com/images/branding/googlelogo"], span[data-href^="https://www.hcaptcha.com/"] > #icon, img.Wirisformula, a[data-testid="headerMediumLogo"]>svg, .d2l-navigation-link-image-container, .d2l-iframe-loading-container, div.mc-ip-hide[style*="background: rgb(255, 255, 255) !important"], [style*="color: rgb(0, 0, 1) !important;"] {
filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
}</style><style class="darkreader darkreader--inline" media="screen">[data-darkreader-inline-bgcolor] {
background-color: var(--darkreader-inline-bgcolor) !important;
}
[data-darkreader-inline-bgimage] {
background-image: var(--darkreader-inline-bgimage) !important;
}
[data-darkreader-inline-border] {
border-color: var(--darkreader-inline-border) !important;
}
[data-darkreader-inline-border-bottom] {
border-bottom-color: var(--darkreader-inline-border-bottom) !important;
}
[data-darkreader-inline-border-left] {
border-left-color: var(--darkreader-inline-border-left) !important;
}
[data-darkreader-inline-border-right] {
border-right-color: var(--darkreader-inline-border-right) !important;
}
[data-darkreader-inline-border-top] {
border-top-color: var(--darkreader-inline-border-top) !important;
}
[data-darkreader-inline-boxshadow] {
box-shadow: var(--darkreader-inline-boxshadow) !important;
}
[data-darkreader-inline-color] {
color: var(--darkreader-inline-color) !important;
}
[data-darkreader-inline-fill] {
fill: var(--darkreader-inline-fill) !important;
}
[data-darkreader-inline-stroke] {
stroke: var(--darkreader-inline-stroke) !important;
}
[data-darkreader-inline-outline] {
outline-color: var(--darkreader-inline-outline) !important;
}
[data-darkreader-inline-stopcolor] {
stop-color: var(--darkreader-inline-stopcolor) !important;
}
[data-darkreader-inline-bg] {
background: var(--darkreader-inline-bg) !important;
}
[data-darkreader-inline-border-short] {
border: var(--darkreader-inline-border-short) !important;
}
[data-darkreader-inline-border-bottom-short] {
border-bottom: var(--darkreader-inline-border-bottom-short) !important;
}
[data-darkreader-inline-border-left-short] {
border-left: var(--darkreader-inline-border-left-short) !important;
}
[data-darkreader-inline-border-right-short] {
border-right: var(--darkreader-inline-border-right-short) !important;
}
[data-darkreader-inline-border-top-short] {
border-top: var(--darkreader-inline-border-top-short) !important;
}
[data-darkreader-inline-invert] {
filter: invert(100%) hue-rotate(180deg);
}</style><style class="darkreader darkreader--variables" media="screen">:root {
--darkreader-neutral-background: var(--darkreader-background-ffffff, #181a1b);
--darkreader-neutral-text: var(--darkreader-text-000000, #e8e6e3);
--darkreader-selection-background: var(--darkreader-background-0060d4, #004daa);
--darkreader-selection-text: var(--darkreader-text-ffffff, #e8e6e3);
}</style><style class="darkreader darkreader--root-vars" media="screen"></style><meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Decompiling a Nintendo 64 Game in 84 Days | Chris' Blog</title><meta name="title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta name="description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta name="keywords" content=""><meta property="og:url" content="http://blog.chrislewis.au/decompiling-a-nintendo-64-game-in-84-days/"><meta property="og:site_name" content="Chris' Blog"><meta property="og:title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta property="og:description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta property="og:locale" content="en_au"><meta property="og:type" content="article"><meta property="article:section" content="blog"><meta property="article:published_time" content="2026-08-26T20:23:59-07:00"><meta property="article:modified_time" content="2026-08-26T20:23:59-07:00"><meta property="og:image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="twitter:title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta name="twitter:description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta itemprop="name" content="Decompiling a Nintendo 64 Game in 84 Days"><meta itemprop="description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta itemprop="datePublished" content="2026-08-26T20:23:59-07:00"><meta itemprop="dateModified" content="2026-08-26T20:23:59-07:00"><meta itemprop="wordCount" content="2266"><meta itemprop="image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="referrer" content="no-referrer-when-downgrade"><style>:root{--width:720px;--font-main:Verdana, sans-serif;--font-secondary:Verdana, sans-serif;--font-scale:1em;--background-color:#fff;--heading-color:#222;--text-color:#444;--link-color:#3273dc;--visited-color:#8b6fcb;--blockquote-color:#222}@media(prefers-color-scheme:dark){:root{--background-color:#01242e;--heading-color:#eee;--text-color:#ddd;--link-color:#8cc2dd;--visited-color:#8b6fcb;--blockquote-color:#ccc}}body{font-family:var(--font-secondary);font-size:var(--font-scale);margin:auto;padding:20px;max-width:var(--width);text-align:left;background-color:var(--background-color);word-wrap:break-word;overflow-wrap:break-word;line-height:1.5;color:var(--text-color)}h1,h2,h3,h4,h5,h6{font-family:var(--font-main);color:var(--heading-color)}a{color:var(--link-color);cursor:pointer;text-decoration:none}a:hover{text-decoration:underline}nav a{margin-right:8px}strong,b{color:var(--heading-color)}button{margin:0;cursor:pointer}time{font-family:monospace;font-style:normal;font-size:15px}main{line-height:1.6}table{width:100%}hr{border:0;border-top:1px dashed}img{max-width:100%}code{font-family:monospace;padding:2px;border-radius:3px}blockquote{border-left:1px solid #999;color:var(--blockquote-color);padding-left:20px;font-style:italic}footer{padding:25px 0;text-align:center}.title:hover{text-decoration:none}.title h1{font-size:1.5em}.inline{width:auto!important}.highlight,.code{border-radius:3px;margin-block-start:1em;margin-block-end:1em;overflow-x:auto}ul.blog-posts{list-style-type:none;padding:unset}ul.blog-posts li{display:flex}ul.blog-posts li span{flex:0 0 130px}ul.blog-posts li a:visited{color:var(--visited-color)}</style><style class="darkreader darkreader--sync" media="screen"></style><style type="text/css">img{margin:0 auto;display:block}@media(prefers-color-scheme:dark){img[src$="#darksafe"]{filter:invert(1)}}.image-caption{font-size:calc(var(--font-scale) * .8);margin:0 20px;font-style:italic;opacity:.9;text-align:center}.highlight{background-color:#272822}.bluesky-comments{margin-top:2.5rem}.bluesky-comments h2{font-size:1.25em;margin-bottom:.5rem}.bluesky-comments__status,.bluesky-comments__summary{font-size:calc(var(--font-scale) * .9)}.bluesky-comments__summary{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem 1rem}.bluesky-comments__reply{display:grid;gap:.75rem;grid-template-columns:44px 1fr;margin-top:1rem}.bluesky-comments__avatar{aspect-ratio:1;border-radius:50%;display:block;height:44px;object-fit:cover;width:44px}.bluesky-comments__avatar--blank{background:var(--blockquote-color);opacity:.25}.bLine truncated
background-color: white !important;
}
.vimvixen-hint {
background-color: var(--darkreader-background-ffd76e, #684b00) !important;
border-color: var(--darkreader-background-c59d00, #9e7e00) !important;
color: var(--darkreader-text-302505, #d7d4cf) !important;
}
#vimvixen-console-frame {
color-scheme: light !important;
}
::placeholder {
opacity: 0.5 !important;
}
#edge-translate-panel-body,
.MuiTypography-body1,
.nfe-quote-text {
color: var(--darkreader-neutral-text) !important;
}
gr-main-header {
background-color: var(--darkreader-background-add8e6, #1b4958) !important;
}
.tou-z65h9k,
.tou-mignzq,
.tou-1b6i2ox,
.tou-lnqlqk {
background-color: var(--darkreader-neutral-background) !important;
}
.tou-75mvi {
background-color: var(--darkreader-background-cfecf5, #0f3a47) !important;
}
.tou-ta9e87,
.tou-1w3fhi0,
.tou-1b8t2us,
.tou-py7lfi,
.tou-1lpmd9d,
.tou-1frrtv8,
.tou-17ezmgn {
background-color: var(--darkreader-background-f5f5f5, #1e2021) !important;
}
.tou-uknfeu {
background-color: var(--darkreader-background-faedda, #432c09) !important;
}
.tou-6i3zyv {
background-color: var(--darkreader-background-85c3d8, #245d70) !important;
}
div.mermaid-viewer-control-panel .btn {
background-color: var(--darkreader-neutral-background);
fill: var(--darkreader-neutral-text);
}
svg g rect.er {
fill: var(--darkreader-neutral-background) !important;
}
svg g rect.er.entityBox {
fill: var(--darkreader-neutral-background) !important;
}
svg g rect.er.attributeBoxOdd {
fill: var(--darkreader-neutral-background) !important;
}
svg g rect.er.attributeBoxEven {
fill: var(--darkreader-selection-background);
fill-opacity: 0.8 !important;
}
svg rect.er.relationshipLabelBox {
fill: var(--darkreader-neutral-background) !important;
}
svg g g.nodes rect,
svg g g.nodes polygon {
fill: var(--darkreader-neutral-background) !important;
}
svg g rect.task {
fill: var(--darkreader-selection-background) !important;
}
svg line.messageLine0,
svg line.messageLine1 {
stroke: var(--darkreader-neutral-text) !important;
}
div.mermaid .actor {
fill: var(--darkreader-neutral-background) !important;
}
mitid-authenticators-code-app > .code-app-container {
background-color: white !important;
padding-top: 1rem;
}
iframe#unpaywall[src$="unpaywall.html"] {
color-scheme: light !important;
}
select {
--darkreader-bg--form-control-background-color: rgba(22, 22, 22, 0) !important;
}
body#tumblr {
--darkreader-bg--secondary-accent: 31, 32, 34 !important;
--darkreader-bg--white: 23, 23, 23 !important;
--darkreader-text--black: 228, 224, 218 !important;
}
:host {
--d2l-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
--d2l-button-icon-background-color-hover: var(--darkreader-bg--d2l-color-gypsum) !important;
--d2l-color-ferrite: var(--darkreader-neutral-text) !important;
--d2l-color-sylvite: var(--darkreader-bg--d2l-color-sylvite) !important;
--d2l-dropdown-background-color: var(--darkreader-neutral-background) !important;
--d2l-dropdown-border-color: var(--darkreader-border--d2l-color-mica) !important;
--d2l-input-backgroud-color: var(--darkreader-neutral-background) !important;
--d2l-menu-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
--d2l-tooltip-background-color: var(--darkreader-neutral-background) !important;
--d2l-tooltip-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
}
:host([_floating]) .d2l-floating-buttons-container {
background-color: var(--darkreader-neutral-background) !important;
border-top-color: var(--darkreader-border--d2l-color-mica) !important;
opacity: 0.88 !important;
}
d2l-card {
background: var(--darkreader-neutral-background) !important;
border-color: var(--darkreader-border--d2l-color-gypsum) !important;
}
d2l-dropdown-content > div,
d2l-menu-item {
background-color: var(--darkreader-neutral-background) !important;
border-radius: 10px !important;
}
d2l-empty-state-simple {
border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
}
.d2l-button-filter > ul > li > a.vui-button {
border-color: var(--darkreader-border--d2l-color-mica) !important;
}
.d2l-label-text:has(.d2l-button-subtle-content):hover,
.d2l-label-text:has(.d2l-button-subtle-content):focus,
.d2l-label-text:has(.d2l-button-subtle-content):active {
background-color: var(--darkreader-bg--d2l-color-gypsum) !important;
}
.d2l-navigation-centerer {
color: inherit !important;
}
.d2l-tabs-layout {
border-color: var(--darkreader-border--d2l-color-gypsum) !important;
}
.d2l-input,
.d2l-calendar-date,
.d2l-htmleditor-container {
background-color: var(--darkreader-neutral-background) !important;
}
.d2l-collapsible-panel {
border: 1px solid var(--darkreader-border--d2l-color-mica) !important;
border-radius: 0.4rem !important;
}
.d2l-collapsible-panel-divider {
border-bottom: 1px solid var(--darkreader-border--d2l-color-mica) !important;
}
.d2l-w2d-flex {
border-bottom: 2px solid var(--darkreader-border--d2l-color-mica) !important;
}
.d2l-collapsible-panel scrolled,
.d2l-collapsible-panel-header,
.d2l-w2d-collection-fixed {
background-color: var(--darkreader-neutral-background) !important;
}
.d2l-loading-spinner-bg {
fill: var(--darkreader-bg--d2l-color-gypsum) !important;
}
.d2l-loading-spinner-bg-stroke {
stroke: var(--darkreader-border--d2l-color-mica) !important;
}
.d2l-loading-spinner-wrapper svg path,
.d2l-loading-spinner-wrapper svg circle {
fill: var(--darkreader-neutral-background) !important;
}
.d2l-twopanelselector-side.d2l-twopanelselector-side-sep {
background: var(--darkreader-bg--d2l-color-mica) !important;
}
.d2l-le-TreeAccordionItem-anchor::before {
background: var(--darkreader-bg--d2l-color-corundum) !important;
}
table[class|="moz-header"] {
background: none;
}
*[style*="color: rgba(0,0,0,"]:not([style*="color: rgba(0,0,0,0)"]),
*[style*="color: rgba(0, 0, 0,"]:not([style*="color: rgba(0, 0, 0, 0)"]),
*[style*="color:#000"],
*[style*="color:black"],
*[style*="color:#282828"],
*[style*="color:#444"],
*[style*="color: #222222"] {
color: var(--darkreader-neutral-text) !important;
}
*[style*="background:#fff"],
*[style*="background-color:#fff"],
*[style*="background: #fff"] {
background: var(--darkreader-neutral-background) !important;
}
*[bgcolor="#F5F5F5"],
*[style*="background-color: #F5F5F5"] {
background: var(--darkreader-background-f5f5f5, #1e2021) !important;
}
*[style*="background: #e6e6e6"] {
background: var(--darkreader-background-e6e6e6, #26292b) !important;
}
body[style*="background: rgb(255, 255, 255) !important"] *:not(a),
.body[style*="background-color: rgb(254, 254, 254) !important"] *:not(a) {
background-color: var(--darkreader-neutral-background) !important;
}
div[style*="www.gstatic.com/android/"] {
background: none !important;
}
*[style*="background:#f9f9f9"] {
background: var(--darkreader-background-f9f9f9, #1b1e1f) !important;
}
body.mediawiki.skin-vector #mw-page-base {
background-image: none !important;
}
.overflow-hidden .spinning-bg,
.overflow-hidden .solid-bg {
background: transparent !important;
}
embed[type="application/pdf"] { filter: invert(100%) contrast(90%); }</style><style></style></head><body><header><a href="https://blog.chrislewis.au/" class="title"><h2>Chris' Blog</h2></a><nav></nav></header><main><h1>Decompiling a Nintendo 64 Game in 84 Days</h1><p><i><time datetime="2026-08-26">26 Aug, 2026</time></i></p><content><p>I’m very pleased to announce that the original <em>Snowboard Kids</em> is now 100% decompiled! This means that all functions<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> have matching C implementations that, when compiled, produce identical machine code to the original game.</p><p>This was obviously not a one-person effort. I am particularly grateful to <a href="https://github.com/inspectredc">inspectredc</a>, <a href="https://github.com/Bl00D4NGEL">Bl00D4NGEL</a>, and <a href="https://github.com/queueRAM">queueRAM</a> for their significant contributions to the project. No amount of AI would have been able to replace them.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> I would also like to thank <a href="https://github.com/iFuzzle">iFuzzle</a>, <a href="https://github.com/JamesBLewis">JamesBLewis</a>, and <a href="https://github.com/douglasjv">douglasjv</a> for lending their tokens to the cause.</p><p>My
hope is that a full decompilation will prove useful to the Snowboard
Kids community. Speedrunners in particular have long focused on the
first game. Working source code can help shed light on externally
observed phenomena such as CPU pathing and the exact factors
contributing to player speed. A full understanding of the source code
will also be useful for static recompilation and more ambitious modding
efforts in the future.</p><p>The speed at which the project was finished is also noteworthy. <em>Snowboard Kids</em> took only 84 days to decompile compared with 596 days for <em>Snowboard Kids 2</em>, roughly one-seventh of the elapsed time.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p><p><a href="https://decomp.dev/cdlewis/snowboardkids-decomp"><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/snowboard-kids-progress_aOx2.svg" alt="chart showing weekly Snowboard Kids decompilation progress"></a></p><p class="image-caption"><a href="https://decomp.dev/cdlewis/snowboardkids-decomp">Weekly progress of the Snowboard Kids decompilation.</a></p><p></p><p>What
explains this difference? Well, it’s 2026, so the answer is at least
partially AI. But it would be a gross oversimplification to attribute
the difference entirely to LLMs.</p><h2 id="what-was-different">What Was Different</h2><p>To
state the obvious, I was not starting from scratch. By this point I had
already spent nearly two years on a similar project and was vastly
faster than when I began. This advantage is difficult to quantify and
was somewhat offset by new challenges such as working with a different
compiler.</p><p>Overall, roughly 4.8% of matching commits involved expert intervention.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
I have already credited these amazing people once, but it’s worth
reiterating. This project would not have been possible without
significant help from the decompilation community, particularly <a href="https://github.com/inspectredc">inspectredc</a>, <a href="https://github.com/queueRAM">queueRAM</a>, and <a href="https://github.com/Bl00D4NGEL">Bl00D4NGEL</a> &lt;3.</p><p>The difficulty generally didn’t come from trying to understand what a function did<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> but rather how that logic was expressed in C and how the resulting code was compiled. <em>Snowboard Kids</em> was compiled with IDO 5.3 rather than the GCC 2.7.2 compiler used by <em>Snowboard Kids 2</em>.</p><p>Most
programmers will be familiar with GCC. It is a widely used open-source
compiler still in active development today. IDO, on the other hand, was a
proprietary compiler developed by SGI, whose own story is closely
entwined with that of the Nintendo 64.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>
Its source was not available, and its original development environment
was tied to obsolete SGI hardware and software. To use and properly
understand it today, the decompilation community has had to
reverse-engineer and <a href="https://github.com/n64decomp/ido">decompile parts of the compiler toolchain</a> as well as <a href="https://github.com/decompals/ido-static-recomp">statically recompile the IDO 5.3 and 7.1 suites</a> to run on modern hardware. That closed history makes IDO harder to reason about than an open compiler such as GCC.</p><p>IDO splits optimisation and code generation across several different passes, transforming code quite aggressively along the way.<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> Tiny changes to the C can then ripple through those passes and produce a completely different register allocation.</p><p>The
community has made great strides in understanding IDO and its quirks,
but this remains more of an art than a science. LLMs and I are not
particularly good at reproducing its output. The usual workflow, for
both me and the agents, was to figure out what a function did and then
write C that approximated that purpose. From there, small tweaks
assisted by the <a href="https://github.com/simonlindholm/decomp-permuter">permuter</a>
could catch any remaining differences. But you can’t permute yourself
into a match in all cases, particularly when the underlying structure is
wrong. IDO’s behaviour made this workflow far less predictable.</p><p>A motivated human team with the right expertise and intuition can match or even exceed the pace of the <em>Snowboard Kids</em> decompilation. The <a href="https://github.com/gcsmith/Pilotwings64Decomp"><em>Pilotwings 64</em></a> decompilation was completed in only 74 days.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> <em>Pilotwings 64</em> had 16% fewer functions than <em>Snowboard Kids</em>, but more compiled code overall, so this is not a clean comparison either.</p><p><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/pilotwings_aOx2.webp" alt="screenshot of Pilotwings 64"></p><p class="image-caption">Pilotwings 64, a charming flight simulator, was a Nintendo 64 launch title and remains a cult classic to this day.</p><p></p><p><em>Snowboard Kids</em> was also smaller than its sequel, containing 2,145 functions compared with 2,995 in <em>Snowboard Kids 2</em>. Function count is a crude measure of difficulty, but there were simply fewer functions to decompile.</p><h2 id="where-agents-did-help">Where Agents Did Help</h2><p>I’ve <a href="https://blog.chrislewis.au/the-long-tail-of-llm-assisted-decompilation/">already written elsewhere</a>
about using agents to decompile functions. The same basic process was
used here, so I’ll focus on what changed. Unlike the previous project,
this one began with access to frontier models and a capable <a href="https://github.com/cdlewis/nigel">agent harness</a>.</p><h3 id="library-code-and-other-low-hanging-fruit">Library Code and Other Low-Hanging Fruit</h3><p>I
was interested to see how agents would fare during the early stages of a
project. One area where they thrived was matching standard-library
code. In theory, this is the most obvious chunk of almost any Nintendo
64 decompilation. The code is not unique to the game, and versions of it
are available online. <em>Snowboard Kids</em> contains more than a hundred source segments from Nintendo’s <code>libultra</code>, alongside functions from the <code>libmus</code> audio library. Tools such as <a href="https://github.com/shygoo/n64sym">N64Sym</a> can identify probable library functions in the ROM.</p><p>This
pass was fairly successful. The main stumbling block was convincing
agents to rely on the existing library source rather than decompile the
same functions again from scratch. This required stronger prompting.
Once a likely library function was identified, agents were instructed to
treat the corresponding source as their starting point and exhaust
plausible SDK versions, compiler options, and conditional compilation
paths before attempting their own implementation.</p><p>Another optimisation was to have an agent write a script that ran <a href="https://github.com/matt-kempster/m2c"><code>m2c</code></a>
against every unmatched function and automatically integrated any exact
matches, rather than relying on agents to attempt those functions
individually.<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>
The script matched only 17 of 1,830 functions, an incredibly low
success rate of 0.93%, but anything matched this way was cheaper than
burning agent tokens.</p><h3 id="ido-tooling-and-skills">IDO Tooling and Skills</h3><p>IDO
is weird, but it is often weird in recurring ways. Successfully
matching one function could reveal a compiler quirk that applied to many
others. Codex has become better at carrying lessons between tasks
through features such as local <a href="https://learn.chatgpt.com/docs/customization/memories">memories</a>. To make those lessons available beyond a single agent, I prompted agents to record observed IDO behaviour in a <a href="https://github.com/cdlewis/snowboardkids-decomp/blob/main/DECOMPILATION_LEARNINGS.md">DECOMPILATION_LEARNINGS.md</a>
file. When an agent discovered a generalisable compiler quirk, it could
record the evidence there for later attempts. This created a useful
feedback loop. Agents helped document IDO, and the resulting
documentation made subsequent agents better at matching IDO code.</p><p>But the most useful resource was <a href="https://github.com/akratch/n64-decomp-workbench">N64 Decomp Workbench</a>,
a collection of tooling and documentation for debugging late-stage MIPS
decompilation mismatches. It can classify mismatches, account for
relocations, replay individual compiler passes, and help distinguish a
structural problem from a register-allocation problem. Pass replay
requires the relevant compiler binaries and project-specific setup, but
once configured it exposes information that a raw assembly diff cannot. A
raw diff tells you that two functions differ. The Workbench can give
agents a much better idea of why the functions differ and what sort of
change might fix them.</p><h3 id="worktrees-and-synchronisation">Worktrees and Synchronisation</h3><p>For
this project, I ran the decompilation harness across four Git
worktrees. Each worktree gave an agent an independent copy of the
repository, allowing several functions to be attempted in parallel.</p><p>One small but useful improvement was to give every task an explicit deadline and expose that deadline to the agent. During the <em>Snowboard Kids 2</em>
project, agents often struggled to use the permuter effectively because
it would continue running until it found a 100% match or was manually
stopped. An explicit deadline allowed agents to set sensible timeouts
and trade off permuting time against other forms of problem-solving.
Anecdotally, it also helped them judge how long to keep working on a
difficult function before giving up. I could then increase the time
allowance as the easy functions disappeared and the remaining work
became more difficult.</p><p>Another problem that had existed in <em>Snowboard Kids 2</em> but became more apparent as I added worktrees was synchronisation. As discussed in my <a href="https://blog.chrislewis.au/the-long-tail-of-llm-assisted-decompilation/">previous post</a>,
each agent was given a function to decompile alongside a set of similar
functions that had already been matched. These provided useful
reference points for reproducing particular IDO instruction patterns.
Work was divided between the worktrees using Nigel’s <code>--shard</code> option, which uses basic hashing to partition candidates between a specified number of workers.</p><p>This
allowed more work to happen in parallel without duplication, but
introduced a new problem as the worktrees diverged. One worktree, for
example, might successfully decompile a function that was 99% similar to
a function being attempted elsewhere, but that new reference would
remain invisible to the other agent until the changes were merged.
Periodically merging everything into the main branch and resynchronising
the worktrees fixed this, but synchronising all four could take more
than an hour. Synchronising continuously wasted time, while waiting too
long increased drift.</p><p>To make synchronisation less critical, I
updated the similarity search to inspect every worktree. A newly matched
function could immediately become a reference for another agent without
waiting for it to reach the main branch. The harness would produce
messages such as the following.</p><div class="highlight"><pre tabindex="0" style="color: rgb(248, 248, 242); background-color: rgb(39, 40, 34); tab-size: 4; --darkreader-inline-color: var(--darkreader-text-f8f8f2, #e8e8d5); --darkreader-inline-bgcolor: var(--darkreader-background-272822, #1c1e1f);" data-darkreader-inline-color="" data-darkreader-inline-bgcolor=""><code class="language-text" data-lang="text"><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">1</span><span>✓ Candidate ["func_80094A94", "func_80094FF4 (../sbk-c)",
</span></span><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">2</span><span> "func_80094808", "func_8009491C (../sbk-a)",
</span></span><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">3</span><span> "func_8009469C (../sbk-c)", "func_80093144"] was fixed!
</span></span></code></pre></div><p>This helped the process remain
efficient as the easy functions disappeared. Synchronisation was still
needed to consolidate and push changes, but it was no longer required
for agents to learn from one another.</p><h3 id="model-choice">Model Choice</h3><p>I
tried GPT-5.5 and 5.6, Claude 4.5 and Fable, and GLM 5.2. This is
completely unscientific. The models were tested against a changing set
of difficult functions, sometimes after another model had already made
partial progress. Broadly, though, Codex continued to outperform Claude,
as it had towards the end of the previous project. Sol xhigh was
particularly effective once it became available.</p><p>GLM 5.2, served
by z.ai, was very disappointing. I had previously been a big fan of GLM.
It was effective, even if it was not quite a frontier model, and its
generous usage limits made up for the gap. That tradeoff became much
less attractive as the limits grew less generous while latency remained
awful. The feedback cycles were so long that I stopped giving it work
and eventually cancelled my subscription.</p><h2 id="what-next">What Next?</h2><p>The
immediate priority is to better document the game. A 100% match means
we have C code for every function; it does not mean we understand what
every function does. There are still generated names to replace, unknown
structure fields to identify, awkward matches to clean up, and large
amounts of data to describe.</p><p>Work is also underway on a <em>Snowboard Kids</em> recompilation. Fortunately, the first game shares many of the quirks addressed by patches in <a href="https://blog.chrislewis.au/snowboard-kids-2-is-recompiled/"><em>Snowboard Kids 2: Recompiled</em></a>.</p><p><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/snowboard-kids-recomp-screenshot_aOx2.webp" alt="early screenshot of Snowboard Kids: Recompiled"></p><p>I’m
looking into porting the levels and other content from the first game
into the second game’s engine, although I have no idea how much work
that will be yet.</p><p>Beyond that, I’m interested in decompiling <em>Snowboard Kids Plus</em> on the PlayStation, a Japan-exclusive expanded release of the first game with extra levels and characters.</p><p><em>If you’ve made it this far, you’re probably interested in decompilation and Snowboard Kids. Take a look at the <a href="https://github.com/cdlewis/snowboardkids-decomp">Snowboard Kids decompilation project</a>. There is still plenty of cleanup and documentation work to do, and contributions are very welcome.</em></p><div class="footnotes" role="doc-endnotes"><hr><ol><li id="fn:1"><p>Almost.
A small amount of handwritten assembly from system libraries remains
because assembly was the original source rather than a temporary
decompilation placeholder.&nbsp;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:2"><p>In the best case, I think the project would have stalled around 89–90% without their IDO expertise.&nbsp;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:3"><p>For <em>Snowboard Kids 2</em>, I measured from the <a href="https://github.com/cdlewis/snowboardkids2-decomp/commit/f1025d16a8aa1d11ec937f8c721af59149feee7c">first commit on 28 September 2024</a> to the <a href="https://github.com/cdlewis/snowboardkids2-decomp/commit/ec405bd1aa8a6a2fed4421e0ba33e37b973db4ec">final matching function on 17 May 2026</a>, a span of 596 days. The 84-day figure for <em>Snowboard Kids</em> is my project-level count; the public repository itself spans from the <a href="https://github.com/cdlewis/snowboardkids-decomp/commit/08f9b60604fd084215770d4a787f7fa9d4d61865">first commit on 28 May 2026</a> to the <a href="https://github.com/cdlewis/snowboardkids-decomp/commit/875002983e210f492aec61ebe78444d2227e270b">final matching function on 19 August 2026</a>, just over 83 days.&nbsp;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:4"><p>Based
on a manual review of the Git history, I identified roughly 41
expert-assisted matches, corresponding to about 4.8% of the commits I
classified as function-matching work. This retrospective classification
is approximate because commits can contain multiple functions, help was
often incorporated into my own commits, and the figure does not measure
person-hours. Note that I am not counting myself as an “expert”.&nbsp;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:5"><p>One notable exception was <a href="https://github.com/cdlewis/snowboardkids-decomp/blob/ba90a9ead954514cfaa1e7c0143957fdfc754092/src/menu/main_menu/controller_main_menu_flow.c#L560"><code>validateControllerPakSave</code></a>,
which appears to have been used to debug Controller Pak saves. Most of
its debugging and validation logic was omitted from the final
build.&nbsp;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:6"><p><a href="https://iwataasks.nintendo.com/interviews/wii/sinandpunishment/0/0/">Nintendo has described</a>
adopting an architecture from Silicon Graphics for the Nintendo 64. The
original development system was similarly SGI-centric. Nintendo’s <a href="https://ultra64.ca/files/documentation/online-manuals/man-v5-1/pro-man/pro01/index.htm">development documentation</a>
describes connecting its emulator board to an SGI Indy workstation, and
IDO was one of the supported compilers. The relationship therefore
extended from the console’s underlying architecture to the machines and
tools used to develop its games.&nbsp;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:7"><p>IDO used a <a href="https://techpubs.jurassic.nl/library/manuals/2000/007-2479-001/sgi_html/ch01.html">multi-stage compilation pipeline</a>. Its <a href="https://techpubs.jurassic.nl/manpages_0530/cat5/uopt.html"><code>uopt</code> documentation</a>
says that eligible loops were unrolled four times by default.
Coincidentally, four is also the number of racers in the game. Loops
with four iterations are therefore candidates for complete unrolling,
and they occur frequently 🫠.&nbsp;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:8"><p>Measured from the <a href="https://github.com/gcsmith/Pilotwings64Decomp/commit/64ae65fbc0dcbb1de772361b1dd84faf0816dc56">addition of progress tracking on 26 January 2026</a> to the <a href="https://github.com/gcsmith/Pilotwings64Decomp/commit/c041f5190e232988fec0cd17b1a5e6ad9182c25d">final matching functions on 10 April 2026</a>, the decompilation took almost exactly 74 days. <em>Pilotwings 64</em> had 1,791 functions compared with 2,145 for <em>Snowboard Kids</em>, but approximately 838 KB of code compared with 732 KB for <em>Snowboard Kids</em>.&nbsp;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:9"><p>Using a one-shot script like this is not a particularly novel idea. I’m pretty sure I copied it from somewhere, possibly <em>Diddy Kong Racing</em>’s <a href="https://github.com/DavidSM64/Diddy-Kong-Racing/blob/84f0ea569b07903ba8a4f9f252e8dc8e4ba54bdc/m2c_all.sh">m2c_all.sh</a>.&nbsp;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">↩︎</a></p></li></ol></div></content><section class="bluesky-comments" data-bluesky-post="https://bsky.app/profile/did:plc:5j7epmcl35kl5pni4y6ehpl2/post/3mu34bwrh4k2m"><h2>Comments</h2><p class="bluesky-comments__status">Loading Bluesky comments...</p></section><script defer="defer" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/bluesky-comments_aOx2.js"></script><div style="font-size:.8em;display:flex;gap:16px;justify-content:center"><p><a href="https://blog.chrislewis.au/snowboard-kids-2-is-recompiled/">&lt;&lt; Previous Post</a></p><p>|</p><p><strike>Next Post &gt;&gt;</strike></p></div><p></p></main><footer></footer><script type="module" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/v3d52b47920f24c319d37e2661827c42b1787588026925_aOx2" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon="{&quot;version&quot;:&quot;2024.11.0&quot;,&quot;token&quot;:&quot;10d5aece9021411ab2fd8081c3b4e020&quot;,&quot;r&quot;:1}" crossorigin="anonymous"></script>
<div>
<div class="vtResetstyles">
<div id="vtoverlay" class="vtoverlay w3-animate-right w3-container vtfadein">
<div class="vtmain" id="vtmain">
<div class="vtcontactus">
<a id="linktest" href="https://www.virustotal.com/">
<img class="vtlogo-popup" style="
width: 24px;
height: 32px;
margin-right: 5px !important;
" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/gti-vt-icon2_aOx2.png">
</a>
<a href="https://www.virustotal.com/">VT4Browsers + Google TI
</a>
<a id="vtclose" class="vtclose" style="margin-left: 15px;"><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: middle">
<path d="M6.4 18.654 5.346 17.6l5.6-5.6-5.6-5.6L6.4 5.346l5.6 5.6 5.6-5.6L18.654 6.4l-5.6 5.6 5.6 5.6-1.054 1.054-5.6-5.6-5.6 5.6Z"></path>
</svg></a>
</div>
<div class="vttextpopup" id="statustext">
<div class="vtscanstatus" id="vtscanstatus">Fetching file...<br></div>
<div class="vtprogress" id="vtprogress"></div>
<br id="brfile" class="vthidden">
<div class="vtscanstatus vthidden" id="vtscanfilelink"></div>
</div>
<div class="vttextpopup vthidden" id="widgettext">
<div id="vtwidgetError"></div>
</div>
</div>
</div>
</div>
<div id="vt-augment-container" class="vtzindex vt-augment drawer" style="background: rgb(49, 61, 90); --darkreader-inline-bgcolor: var(--darkreader-background-313d5a, #273148); --darkreader-inline-bgimage: none;" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage=""></div></div><span class="vttooltiptext vthidden">&nbsp;Click to open IoC report&nbsp;<br>with VirusTotal Augment</span><style></style><style class="darkreader darkreader--sync" media="screen"></style></body></html>