Files
goldeneye-pc-port/docs/reference/Decompiling a Nintendo 64 Game in 84 Days _ Chris' Blog.htm
T
James Dansereau 64698f6b23 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.
2026-08-29 09:52:47 -04:00

510 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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}.bluesky-comments__meta,.bluesky-comments__text{margin:0}.bluesky-comments__meta{display:flex;flex-wrap:wrap;font-size:calc(var(--font-scale) * .85);gap:.25rem .5rem}.bluesky-comments__meta span{opacity:.8}.bluesky-comments__text{margin-top:.25rem}.blog-posts{display:grid;gap:2rem}.blog-post-card{display:grid;gap:1rem}.blog-post-card__image-link{display:block}.blog-post-card__image{aspect-ratio:16/9;border-radius:6px;display:block;height:auto;margin:0;object-fit:cover;width:100%}.blog-post-card__date{display:block;margin-bottom:.35rem;opacity:.78}.blog-post-card__title{font-size:1.35em;line-height:1.25;margin:0}.blog-post-card__title a:visited{color:var(--visited-color)}.blog-post-card__excerpt{margin:.6rem 0 0}@media(min-width:680px){.blog-post-card{align-items:start;grid-template-columns:minmax(220px,.9fr)minmax(0,1.1fr)}}</style><style class="darkreader darkreader--sync" media="screen"></style><meta name="darkreader" content="2d9b906155c94b4aa93b34422c79a3e7"><style class="darkreader darkreader--override" media="screen">.jfk-bubble.gtx-bubble {
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>