forked from Ports/goldeneye-pc-port
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.
510 lines
42 KiB
HTML
510 lines
42 KiB
HTML
<!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> <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. <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. <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. <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”. <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. <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. <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 🫠. <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>. <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>. <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/"><< Previous Post</a></p><p>|</p><p><strike>Next Post >></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="{"version":"2024.11.0","token":"10d5aece9021411ab2fd8081c3b4e020","r":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"> Click to open IoC report <br>with VirusTotal Augment</span><style></style><style class="darkreader darkreader--sync" media="screen"></style></body></html> |