Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
03dcd16
Add notes/CONVENTIONS.md: lifecycle policy for notes/, generalize scr…
andrewboudreau Sep 6, 2026
072a8c8
check_dead_references: scan config JSON, .jsonl and tools JS
andrewboudreau Sep 6, 2026
defb286
Fix three defects in notes/ lifecycle policy: MERGE disposition, reti…
andrewboudreau Sep 6, 2026
ab50d80
Add notes/ registry + generated index (tools/notes_index.py), wire th…
andrewboudreau Sep 6, 2026
869b48c
Add notes-bench harness and condense mwccarm-codegen.md into codegen-…
andrewboudreau Sep 6, 2026
84d926d
Condense tu-promotion-conventions.md and runbook-type-reconstruction.md
andrewboudreau Sep 6, 2026
40908d6
notes: retire zero-inbound handoffs, banner three stale-but-cited ones
andrewboudreau Sep 6, 2026
85bd9b5
notes: complete 4 MERGE-eligible consolidations, dedupe the ActorBase…
andrewboudreau Sep 6, 2026
d660cd7
notes: regenerate registry and README for the retirements and merges …
andrewboudreau Sep 6, 2026
ff200f5
notes: drop the builder.md romExtent dedupe -- landing via the docs P…
andrewboudreau Sep 6, 2026
3e54448
notes: dedupe sinit-ownership-cases.md, 47,569 -> 27,992 bytes
andrewboudreau Sep 6, 2026
258121a
notes: stop both harnesses crying wolf on unrelated work
andrewboudreau Sep 6, 2026
ffa9e3a
notes: regenerate the index for main's new notes files
andrewboudreau Sep 6, 2026
b8d4156
notes: yield the C/C++ comment surface to main's implementation
andrewboudreau Sep 7, 2026
3991b40
Fix deterministic notes accounting and structured reference checks
andrewboudreau Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ notes/recon-2026-06-13.md
notes/overnight-plan.md
notes/ac-decomp-comparison.md

# Session scratch that must never reach notes/ -- see notes/CONVENTIONS.md.
# Tracking docs, per-agent work queues, and personal drafts belong in an agent's
# scratchpad, not committed notes/. These patterns are a backstop for habit slips,
# not the primary control.
notes/goal-*.md
notes/*.local-draft.md
notes/*_agent[0-9]*.tsv
notes/*_agent[0-9]*.md

# third-party tools cloned for evaluation/integration (not part of this repo)
vendor/

Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ automates batch coordination, ROM verification, `src/` conventions, and PR
formatting, and connects you to the repo's toolchain over MCP. Hand-driving
the tools below works fine too.

**Looking for a deeper reference** (matching craft, C++ conversion, TU
promotion, provenance)? Start at [`notes/README.md`](notes/README.md) — it
routes by task instead of making you scroll 130+ files.

## The one rule that matters

**Every file you add to `src/` must byte-reproduce the ROM.**
Expand Down
42 changes: 42 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# CLAUDE.md

This is `sm64ds-decomp`: a from-scratch decompilation of Super Mario 64 DS. The
goal is source that reads like it shipped from Nintendo EAD in 2004 *and*
compiles byte-identical to the retail cartridge under the pinned mwccarm
compiler. Both halves are required; when they conflict, byte accuracy wins,
because it's the only half a machine can check.

## Before you touch anything

- **Read [`AGENTS.md`](AGENTS.md) first.** It states the one rule that gates
every PR (`src/` must byte-reproduce the ROM) and what a change looks like.
- **No ROM or Nintendo asset lives in this repo.** Everything byte-verified
runs against a cartridge dump you or the human supplies locally; it is
git-ignored and never committed.
- **A worktree needs gitignored inputs wired in** (`extracted/`, the pinned
`tools/mwccarm/`, `tools/bin/dsd.exe`) before any byte gate will run — see
the `decomp-worktree` skill if one is installed, or
[`notes/worktree-inputs.md`](notes/worktree-inputs.md).
- **Verify locally before claiming something works:**
`python tools/rombuild.py -j16 --no-rom`.

## Where things live

- [`AGENTS.md`](AGENTS.md) — PR shape and the merge gate, written for an AI
agent's human to read first.
- [`notes/README.md`](notes/README.md) — the index into ~140 reference notes
(matching craft, C++ conversion, TU promotion, provenance), grouped by task.
Start there instead of grepping `notes/` blind.
- [`notes/CONVENTIONS.md`](notes/CONVENTIONS.md) — the lifecycle policy for
`notes/` itself: what kind of note to write, when to retire one, and why a
session's scratch work does not belong here (it belongs in your own
scratchpad or a gitignored path).
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — environment setup. [`MERGE.md`](MERGE.md)
— the review/merge playbook, if you are landing or coordinating PRs.

## The one thing that surprises people

A byte-match alone does not prove a relocation calls the right symbol —
`tools/match.py` wildcards relocated words. If your function calls anything or
touches globals, also run `python tools/prepush_linkcheck.py --range
origin/main..HEAD` before opening a PR.
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ written as a love letter to the original EAD team: every file in `src/`
should read like idiomatic, period-accurate C++ from 2004 *and* compile
byte-identical to the retail ROM under the pinned compiler. See
[`AGENTS.md`](AGENTS.md) for what a change looks like and what the merge gate
checks — this file is setup and ground rules.
checks — this file is setup and ground rules. For deeper reference material
(matching craft, C++ conversion, provenance), see
[`notes/README.md`](notes/README.md).

## Get in touch

Expand Down Expand Up @@ -76,10 +78,11 @@ python tools/rombuild.py -j16 --no-rom
```

Before writing any code, skim
[`notes/mwccarm-codegen.md`](notes/mwccarm-codegen.md) — it documents how this
[`notes/codegen-rules.md`](notes/codegen-rules.md) — it documents how this
exact compiler turns C++ into bytes (struct copies, vtable/PMF dispatch,
destructor-variant order, the register-allocation wall). Writing with those
habits in mind gets a first draft much closer.
habits in mind gets a first draft much closer. The condensed rules live there;
the dated discovery log they're mined from is `notes/mwccarm-codegen.md`.

## Easy pickings: the near-miss database

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Everything here runs against a cartridge dump you supply yourself, which stays o
machine and is git-ignored.

New here? Start with **[CONTRIBUTING.md](CONTRIBUTING.md)**, and if you review or
merge PRs read **[MERGE.md](MERGE.md)**.
merge PRs read **[MERGE.md](MERGE.md)**. Deeper reference material (matching craft,
C++ conversion, provenance) is indexed at **[notes/README.md](notes/README.md)**.

## Progress

Expand Down Expand Up @@ -121,10 +122,10 @@ as much as possible before any manual effort:

An attempt that compiles to almost the right bytes is evidence. Every one is recorded
in the near-miss database with how far off it landed and what was tried, so the next
person does not rediscover the same dead end. The compiler behaviour recovered along
the way is written up in [notes/mwccarm-codegen.md](notes/mwccarm-codegen.md), which is
where the register allocation, instruction scheduling, and materialization findings
live.
person does not rediscover the same dead end. The recovered compiler behaviour is
condensed into rules at [notes/codegen-rules.md](notes/codegen-rules.md) — register
allocation, instruction scheduling, and materialization findings. The full dated
discovery log is [notes/mwccarm-codegen.md](notes/mwccarm-codegen.md).

### Two checks beyond the byte diff

Expand Down
12 changes: 0 additions & 12 deletions config/dead-reference-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@
"file": "notes/actor-vtables.md",
"ref": "src/_ZN8PlatformD0Ev.cpp"
},
{
"file": "notes/archive/crack-loop-runbook.md",
"ref": "notes/pret-idioms.md"
},
{
"file": "notes/archive/func_02059d8c-asm-origin.md",
"ref": "src/dst"
Expand Down Expand Up @@ -101,10 +97,6 @@
"file": "notes/bgobject-provenance.md",
"ref": "src/FortressWall_Spawn.c"
},
{
"file": "notes/bgobject-provenance.md",
"ref": "src/func_ov025_02111898.c"
},
{
"file": "notes/collision-query-classes.md",
"ref": "notes/drafts-sphereclsn-detectclsn.cpp"
Expand Down Expand Up @@ -185,10 +177,6 @@
"file": "notes/handoff-sphereclsn-detectclsn.md",
"ref": "notes/drafts-sphereclsn-detectclsn.cpp"
},
{
"file": "notes/handoff-validator-and-tests.md",
"ref": "tools/test_"
},
{
"file": "notes/match-attempts.md",
"ref": "notes/adopting-match-conventions.md"
Expand Down
2 changes: 1 addition & 1 deletion config/profile-campaign-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"actual": [
"FlyGuy_Spawn"
],
"note": "ledger AHEAD of the tree: the campaign name was written into this ledger row but never reached symbols.txt. FIXED in PR #2238 (cpp/profile-reconstruction-wave31), which renames it in symbols.txt, in the promoted intact-object TU src/actors/daPropeller_Heyho_c.cpp that defines it, and in that TU manifest. This entry is retained only because #2238 is not yet merged; drop it in the same change that lands #2238. Every other entry banked with this gate has been adjudicated and removed."
"note": "ledger AHEAD of the tree: the campaign name was written into this ledger row but never reached symbols.txt. FIXED in PR #2238 (cpp/profile-reconstruction-wave31), which renames it in symbols.txt, in the promoted intact-object TU src/game/actors/d_a_propeller_heyho.cpp that defines it, and in that TU manifest. This entry is retained only because #2238 is not yet merged; drop it in the same change that lands #2238. Every other entry banked with this gate has been adjudicated and removed."
}
]
}
2 changes: 1 addition & 1 deletion config/tu_manifest.d/ov006/MgBingoBallSlotsShot.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"boundary_evidence": [
"contiguous linker run: 0x02119824..0x02119904, 2 function(s), from config/arm9/overlays/ov006/delinks.txt and symbols.txt (hand-built manifest entry -- NOT re-derivable from a fresh build/tu_map.json run)",
"no class label (unattributed run; two plain functions, no vtable/RTTI anchor)",
"HAND-WRITTEN ENTRY, NOT tubuild.py create output: a freshly regenerated build/tu_map.json (rtti_extract -> rtti_vtables -> tu_map, this same commit) no longer isolates this address range as its own unit -- it now falls INSIDE a 132-function, 14-class run (0x210d740-0x2119900: cMgSmartball_* family + dScMgSmartball_c) that tu_map's own meta.under_segmented list flags for module ov006 (TU count is a LOWER BOUND, per decomp-tu-slicing). The original tier1_under_cliff candidate (notes/data/tu-merge-candidates.json) was generated from an EARLIER tu_map snapshot that cut this module much finer (87 units vs 71 now) -- the same stale-map-over-cut failure mode notes/stale-tu-map-overcut-ov006.md documents elsewhere in ov006. This TU's 2-function boundary is therefore UNCONFIRMED by the current tool: text-level compile/verify below is real, but do not read it as proof this is the ROM's actual original TU cut -- it may be a fragment of the larger unresolved run."
"HAND-WRITTEN ENTRY, NOT tubuild.py create output: a freshly regenerated build/tu_map.json (rtti_extract -> rtti_vtables -> tu_map, this same commit) no longer isolates this address range as its own unit -- it now falls INSIDE a 132-function, 14-class run (0x210d740-0x2119900: cMgSmartball_* family + dScMgSmartball_c) that tu_map's own meta.under_segmented list flags for module ov006 (TU count is a LOWER BOUND, per decomp-tu-slicing). The original tier1_under_cliff candidate (notes/data/tu-merge-candidates.json) was generated from an EARLIER tu_map snapshot that cut this module much finer (87 units vs 71 now) -- the same stale-map-over-cut failure mode seen elsewhere in ov006. This TU's 2-function boundary is therefore UNCONFIRMED by the current tool: text-level compile/verify below is real, but do not read it as proof this is the ROM's actual original TU cut -- it may be a fragment of the larger unresolved run."
],
"sections": [
{
Expand Down
2 changes: 1 addition & 1 deletion config/tu_manifest.d/ov006/MgWhichWiggler.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"boundary_evidence": [
"contiguous linker run: 0x020ede18..0x020edec0, 2 function(s), from config/arm9/overlays/ov006/delinks.txt and symbols.txt (hand-built manifest entry -- NOT re-derivable from a fresh build/tu_map.json run)",
"no class label (unattributed run; two plain functions, no vtable/RTTI anchor)",
"HAND-WRITTEN ENTRY, NOT tubuild.py create output: a freshly regenerated build/tu_map.json (rtti_extract -> rtti_vtables -> tu_map, this same commit) no longer isolates this address range as its own unit -- it now falls INSIDE a 180-function, 7-class run (0x20e6c28-0x20eebe8: MgPsycheOut, dScMg3DEsp_c, dScMgD3DBase_c, dScMgHanachan_c, dScMgJump_c) that tu_map's own meta.under_segmented list flags for module ov006 (TU count is a LOWER BOUND, per decomp-tu-slicing). The original tier1_under_cliff candidate (notes/data/tu-merge-candidates.json) was generated from an EARLIER tu_map snapshot that cut this module much finer (87 units vs 71 now) -- the same stale-map-over-cut failure mode notes/stale-tu-map-overcut-ov006.md documents elsewhere in ov006. This TU's 2-function boundary is therefore UNCONFIRMED by the current tool: text-level compile/verify below is real, but do not read it as proof this is the ROM's actual original TU cut -- it may be a fragment of the larger unresolved run."
"HAND-WRITTEN ENTRY, NOT tubuild.py create output: a freshly regenerated build/tu_map.json (rtti_extract -> rtti_vtables -> tu_map, this same commit) no longer isolates this address range as its own unit -- it now falls INSIDE a 180-function, 7-class run (0x20e6c28-0x20eebe8: MgPsycheOut, dScMg3DEsp_c, dScMgD3DBase_c, dScMgHanachan_c, dScMgJump_c) that tu_map's own meta.under_segmented list flags for module ov006 (TU count is a LOWER BOUND, per decomp-tu-slicing). The original tier1_under_cliff candidate (notes/data/tu-merge-candidates.json) was generated from an EARLIER tu_map snapshot that cut this module much finer (87 units vs 71 now) -- the same stale-map-over-cut failure mode seen elsewhere in ov006. This TU's 2-function boundary is therefore UNCONFIRMED by the current tool: text-level compile/verify below is real, but do not read it as proof this is the ROM's actual original TU cut -- it may be a fragment of the larger unresolved run."
],
"sections": [
{
Expand Down
2 changes: 1 addition & 1 deletion config/tu_manifest.d/ov006/dScMgCard_c.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
"MEASURED CW 1.2 ABI WALLS: genuine out-of-line dMgCardObj_c/dMgDilarCardObj_c constructors and destructors make the derived bodies 0x24 bytes by calling the base C2/D2, not the cartridge's 0x1c-byte folded stores. Marking the base lifecycle inline makes all four C1/D1 bodies byte-identical, but mwccarm emits base D1 between the scene D1 and scene D0 instead of the cartridge's scene-D1, scene-D0, base-D1, derived-D1 order. Swapping source definitions and the force-inline pragmas do not change that order. A semantic placement-construction factory is also unavailable: this compiler rejects class, global, and placement operator-new declarations and supplies no placement-new builtin. The four C1/D1 callbacks and dScMgCard_c_classInit are consequently the smallest literal ABI bridges; the classes, ownership, behavior, layout and shared state remain genuine typed C++.",
"FOCUSED VERIFY LIMITATION: tools/tubuild.py verify reports 32/37 because objisolate rejects an R_ARM_ABS32 relocation with addend zero against a vtable emitted by the same object. The five affected functions are exactly the four retained C1/D1 callbacks plus dScMgCard_c_classInit. Their extracted bytes match individually and all 37 emitted text sections occur in cartridge order; the production linker and full ROM build are the authority for their same-object relocation resolution.",
"2026-08-31 conversion to real members: the twenty-eight tubuild extern \"C\" free functions became real member functions -- dScMgCard_c's own scene logic (RenderHud, UpdateState, the hand-evaluation family SettleHand/ArrangeHand/MarkSingles/EvaluateHand/CompareHands/AllLifted/AllInState/CountInState/FindInState/DrawCardValue/FillWeights, the adjudicated vtable-slot events OnYoshiTryEat/OnTurnIntoEgg/OnGroundPounded) and the element motion members FlyOff/SetHome/Redeal/DealIn/GetOffscreenY/Render/Update over the RTTI-confirmed element classes dMgCardObj_c/dMgDilarCardObj_c. The header retype of mArray1/mArray2 from raw byte arrays to the element classes is destructor-neutral (measured) and unlocks UpdateState's member spellings. Each function was byte-verified with tools/match.py as it converted, and the hash dumps bracketing the final respells show every then-enrolled function section identical to the matched baseline.",
"COINED NAMES, the one thing not provable: the twenty-eight renamed functions' ROM symbols are address-only (func_ov006_020d96e0..func_ov006_020db9dc), so no recovered spelling exists to confirm or contradict the behavioral names. RenderHud, UpdateState, SettleHand, ArrangeHand, MarkSingles, EvaluateHand, CompareHands, AllLifted, AllInState, CountInState, FindInState, DrawCardValue, FillWeights, FlyOff, SetHome, Redeal, DealIn and the element Render/Update/GetOffscreenY are coined from what each function does; OnYoshiTryEat/OnTurnIntoEgg/OnGroundPounded are the family's adjudicated legacy recovered names (vtable slots 18/19/21, notes/minigame-family-decisions.md); the two element D1 symbols are the compiler's own mangling of the classes this TU's _ZTI/_ZTS proves. The coinage is disclosed here, in the per-function banner comments, and in symbols/actor_renames.tsv's why column.",
"COINED NAMES, the one thing not provable: the twenty-eight renamed functions' ROM symbols are address-only (func_ov006_020d96e0..func_ov006_020db9dc), so no recovered spelling exists to confirm or contradict the behavioral names. RenderHud, UpdateState, SettleHand, ArrangeHand, MarkSingles, EvaluateHand, CompareHands, AllLifted, AllInState, CountInState, FindInState, DrawCardValue, FillWeights, FlyOff, SetHome, Redeal, DealIn and the element Render/Update/GetOffscreenY are coined from what each function does; OnYoshiTryEat/OnTurnIntoEgg/OnGroundPounded are the family's adjudicated legacy recovered names (vtable slots 18/19/21); the two element D1 symbols are the compiler's own mangling of the classes this TU's _ZTI/_ZTS proves. The coinage is disclosed here, in the per-function banner comments, and in symbols/actor_renames.tsv's why column.",
"D3 declarations: Sound::PlayBank2_2D, GX::LoadOBJPltt, GXS::LoadOBJPltt, G2x::SetBlendAlpha, ApproachLinear and ApproachLinear2 are declared at their RATIFIED_TRUE_NAMES forms (parameter types read off the mangled names, see the engine-helpers banner in the source); func_ov006_020c0aa8 and func_ov006_020c1804 keep the void* spellings shared with dScMgBSC_c/dScMgMCarlo2_c, and func_ov006_020c1a88 this TU's extern int (char*) form, because redeclaring a C-linkage symbol with different parameter types is an illegal overloading. The local shadow types (Node, Five, Struct30, OamAttrTmpl, Slot6, Obj6 and kin) are byte-exact struct spellings of raw-offset object headers the family has not yet carved; retiring them belongs to a header carve, not this TU.",
"tubuild reconcile note: the warnings above named the legacy file by its function's old auto-generated symbol func_ov006_020db720, which was renamed to _ZN11dScMgCard_c13OnTurnIntoEggEi (ov006 0x020db720). The rename rewrote that name inside the conflict markers in src/minigames/d_s_mg_card.cpp, because those are comment text, but not inside these notes, which are prose. Only the spelling changed: the remaining conflicts are still OPEN and their markers still stand. Checked by tools/check_tubuild_conflicts.py.",
"tubuild reconcile note: several conflict markers the warnings above were frozen against are no longer present in src/minigames/d_s_mg_card.cpp. They were reconciled in place and the notes were not updated at the time, so those entries claimed open conflicts that no longer existed. Marked (RESOLVED) rather than deleted, so the record that two legacy files disagreed about these names survives. Checked by tools/check_tubuild_conflicts.py."
Expand Down
2 changes: 1 addition & 1 deletion include/ChiefChilly.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* 0x3d8/3dc/3e0 (mSpawnPos*) are NOT a Vector3 despite looking like one: the
* destructor does not touch 0x3d8, and a Vector3 member would be destroyed there.
*
* Field provenance: notes/enemy-leaf-provenance.md.
* Field provenance: notes/enemy-provenance.md.
*
* SM64DS RTTI names the implementation daKing_Donketu_c. The reconstructed
* factory daKing_Donketu_c_classInit (historical alias
Expand Down
2 changes: 1 addition & 1 deletion include/Goomboss.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* 0x3d8 is the MaterialChanger's Animation cursor (+0x08) and 0x3f0 is the
* TextureSequence's playback speed (+0x0c); see include/Animation.h.
*
* Field provenance: notes/enemy-leaf-provenance.md.
* Field provenance: notes/enemy-provenance.md.
*
* SM64DS RTTI names the implementation daKuriKing_c. The reconstructed
* factory daKuriKing_c_classInit_KURIKING (historical alias
Expand Down
2 changes: 1 addition & 1 deletion include/JetStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct JetStream : dEnemyBase_c {
and never read back anywhere in the tree, so what they select is unknown:
0x314 takes nibble 3 (param1 >> 12 & 0xf), 0x318 takes bit 0 but only when
the low nibble is 0 or 1. Deliberately left unk_ -- see
notes/enemy-leaf-provenance.md. */
notes/enemy-provenance.md. */
s32 unk_314; /* 0x314 */
s32 unk_318; /* 0x318 */

Expand Down
2 changes: 1 addition & 1 deletion include/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ typedef char Player_State_size_must_be_0x18[sizeof(Player::State) == 0x18 ? 1 :
* anim 0x28 and a launch speed and plays a character voice. That is a wall
* jump.
*
* This is the failure mode described in notes/overlay-ambiguous-references.md.
* This is the failure mode described in notes/overlay-residency.md.
*
* St_Null_Init was the SAME defect and moved in the same commit: ov006 had it
* at 0x020cac30 (size 0x6c), ov002 has an eight-byte `return 1` there,
Expand Down
2 changes: 1 addition & 1 deletion include/Whomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* - unk_338 = TextureSequence.currFrame
* - unk_33c = TextureSequence.speed
*
* Field provenance: notes/enemy-leaf-provenance.md.
* Field provenance: notes/enemy-provenance.md.
*
* SIZE IS THE ROM'S OWN: `daBtn_c_classInit_BATAN` calls
* `fBase_c::operator new(1552)` -- 0x610 -- and stores this class's
Expand Down
Loading
Loading