# AGENTS.md - SFA-Decomp Runbook

Keep this light. The project is still in the "recover the game" phase, not the "polish an already-understood decomp" phase.

## Target
- Active target version in this repo is `EN v1.0` (`GSAE01`).
- `orig/` may contain EN, PAL, and JP artifacts. Use them for cross-checking only.
- `resources/ghidra-decomp-4-12-2026/` contains the raw EN v1.0 Ghidra decomp with no code analysis. Use it for basic code structure only.
- `resources/DolphinSymbolExport_GSAE01.txt` contains the active EN symbol export based on Dolphin Signature analysis. This is good for SDK matching, but the addresses need to be translated against the current `config/GSAE01/symbols.txt` anchors before using them for splits.
- Other `resources/*` content should also be treated as rough shape data, not analysis.
- `reference_projects/*` may exist in some checkouts to mine patterns, especially SDK code and common Nintendo/GameCube layouts. Do not edit anything in this folder, as it is reference code from other game decompilation efforts. Known donors: `marioparty4` (2002, closest-era Nintendo title), `tww` (2002–03 Wind Waker), `melee` (2001, oldest SDK baseline), `mkdd` (2003 Mario Kart DD), `pikmin2` (2004, richest Dolphin SDK coverage — `src/Dolphin/` mirrors our layout), `prime` (2002 Metroid Prime, same year as SFA).
- SDK files have already been added to this project, but they are from another game. We will need to assign our splits to use these files, update symbols, and possibly learn from `reference_projects/*` to drive our SDK files to matching.
- `reference_projects/rena-tools*` may exist in some checkouts with Rena's SFA decomp projects, and may have some information that helps.

## What Progress Looks Like
- Recover real functions, data, class boundaries, globals, vtables, and file structure.
- Improve buildability, linkage, and objdiff results.
- Make the source look more like plausible original code, not a pile of coercion hacks.
- Generate new understanding when the repo has none yet.

This repo starts from very little. Expect to do naming, struct recovery, type cleanup, file splitting, and tool building as part of normal work.

## Ground Truth
- `objdiff` is still the final measure of whether a change helped.
- Ghidra output is raw input. It will often be wrong about types, signatures, control flow details, and boundaries.
- There are no symbol maps to lean on here. Do not write the workflow as if names or section layout are already known.
- Reference projects are evidence, not truth. Match version, compiler behavior, ABI, and surrounding code before borrowing anything.

## Working Style
- Start from one promising function, object, data block, or subsystem.
- Work outward aggressively if the blocker is adjacent code, missing types, unknown globals, constructor patterns, SDK reuse, or bad file boundaries.
- Do not get trapped in local optima. If a path stops yielding structure, switch level: inspect related code, assets, rodata, strings, object layouts, SDK analogs, or write tooling.
- Prefer recovering coherent source over narrowly chasing one assembly diff while the surrounding code remains obviously wrong.
- Please try to keep the build in a functional state (ninja should compile successfully on work complete and a fresh run).

## Expected Work
- Infer function names where none exist yet.
- Recover data definitions and rodata ownership.
- Establish class and subsystem boundaries.
- Replace guessed offsets and anonymous blobs with real fields when justified.
- Identify reusable SDK or middleware code from `reference_projects/*`.
- Mine strings, assets, tables, and binary patterns when that reveals structure.
- Use `python tools/orig/source_leaks.py` when checking whether `orig/*` still preserves direct source/header names before leaning on external debug-side references.
- Use `python tools/orig/source_matrix.py` when PAL / JP / EN rev1 may strengthen or rename a weak source-tag clue before you commit to a file name or materialize a stub.
- Use `python tools/orig/source_recovery.py` when `main.dol` source-tagged strings may give EN file/function anchors for naming or splits.
- Use `python tools/orig/source_boundaries.py` when you need those retail EN source tags turned into concrete current EN work windows and split coverage status before planning a first-pass source skeleton, including cross-bundle nearby-string context and low-confidence indirect neighborhoods for no-direct-xref tags like `n_attractmode.c`.
- Use `python tools/orig/source_skeleton.py` when retail source-tagged xrefs need to be grouped into current EN address islands so you can plan first-pass source skeletons or split windows instead of treating each clue in isolation.
- Use `python tools/orig/source_corridors.py` when a retail-backed EN span needs debug-side file-size or short source-order context so you can tell whether the current seed is too small, too wide, or sitting next to one obvious missing source file.
- Use `python tools/orig/source_windows.py` when a retail-backed EN span also has an exact debug-side split and you want ranked current EN whole-file window candidates instead of only a seed-too-small / seed-too-wide verdict.
- Use `python tools/orig/source_gap_packets.py` when two retail-backed anchors leave one or a few plausible missing files between them and you want resolved debug-side path hints plus the current EN gap functions for immediate split planning, or `--materialize-all` to emit ready corridor briefs under `docs/orig/source_gap_packet_briefs/`.
- Use `python tools/orig/source_gap_windows.py` when a short retail-backed gap packet already names the missing files and you want estimated current EN per-file windows instead of one large anonymous gap span, or `--broad-exact-intervals` when the same anchors bound a larger exact debug interval you want projected into EN windows, or `--materialize-all` to emit ready briefs under `docs/orig/source_gap_window_briefs/`.
- Use `python tools/orig/source_layout.py` when you want one address-ordered per-file skeleton that interleaves retail-backed anchor windows with the short-gap file estimates so you can start claiming source boundaries directly, or `--broad-exact-layout` when you want that same layout view to include larger exact-debug corridor windows, or `--materialize-all` to emit ready layout briefs under `docs/orig/source_layout_briefs/`.
- Use `python tools/orig/source_functions.py` when you want a tighter report of retail-labeled function candidates such as `setBlendMove` / `Init` with their current EN xref clusters.
- Use `python tools/orig/source_worklist.py` when you want one prioritized queue of retail-backed boundary jobs, or `--materialize-all` to emit ready markdown packets under `docs/orig/source_worklist_packets/` for handoff.
- Use `python tools/orig/source_blueprints.py` when you want one address-ordered neighborhood view that merges anchor windows and short gap packets, or `--materialize-all` to emit ready neighborhood briefs under `docs/orig/source_blueprint_briefs/`.
- Use `python tools/orig/source_reference_hints.py` when retail EN evidence names a file but you want clearly-labeled reference-project path, function, DLL, or object hints without promoting them to source-truth.
- Use `python tools/sdk_import_probe.py` when testing candidate SDK source files against current EN windows by compiled size, function-count shape, or assigned-split audits before changing `splits.txt`.
- Use `python tools/sdk_dol_match.py` when reference projects also have known `splits.txt` plus `orig/*/sys/main.dol`; it compares normalized PPC instruction signatures across reference SDK windows and current EN windows, and `source-likely` results are strong candidates for retargeting or source import.
- Use `python tools/orig/source_object_packets.py` when a retail source tag needs to be tied back to current EN object/class/DLL packets or materialized into a non-built source packet under `src/main/unknown/source_packets/`.
- Use `python tools/orig/source_materialize.py` when you want to materialize retail-backed stubs under `src/` and/or export exact disc source/header artifacts to a local non-source folder.
- Use `python tools/orig/object_family_packets.py` when you want retail-backed object/DLL family packets or non-built `src/main/unknown/` boundary stubs for exploratory split planning before a real source filename is proven.
- Use `python tools/orig/object_def_packets.py` when you want retail-backed per-object exploratory stubs under `src/main/unknown/objects/`, cross-linked to class packets, DLL families, placement widths, and EN descriptor slots for rapid split planning.
- Use `python tools/orig/object_bin2_audit.py` when `OBJECTS.bin2` may clarify a shaky object boundary, inline substructure, or exploratory packet by comparing the sibling retail object-table lineage against live `OBJECTS.bin`.
- Use `python tools/orig/tab_catalog.py` when you need real retail chunk boundaries from `.tab` / `.bin` families before proposing asset splits.
- Use `python tools/xref/asset_clusters.py` when you want regenerable `src/xref/` JSON packets plus `docs/xref/` handoff briefs that link retail EN DOL source/file-string xrefs to current EN functions, `orig/files` asset families, and Rena file-slot metadata so you can cluster loader code, singleton-ish systems, or source islands before naming or splitting them.
- Use `python tools/orig/dol_vtables.py --stores-only` when hunting constructor-backed class boundaries, vtables, or callback tables in the retail DOL.
- Use `python tools/orig/constructor_packets.py` when a store-backed DOL vtable/callback-table hit should become a non-built `src/main/unknown/constructors/` packet for class-boundary or hierarchy recovery.
- Use `python tools/orig/romlist_params.py` when recovering object placement structs, param widths, or variable-length romlist families from retail data.
- Write small custom tools and scripts under the tools/ folder when the repo lacks the visibility needed to move quickly.

## Rules
- Bias toward EN `GSAE01` for addresses, sizes, and matching decisions.
- Prefer real definitions and linkage over `extern` placeholders.
- Do not hardcode addresses or invent junk `lbl_` / `fn_` names just to force progress.
- Do not commit literal recovered source/header artifacts from `orig/` into `src/`; keep them in manifests/docs or export them to a local non-source folder when needed.
- When materializing retail-backed stubs without a proven directory, keep them at `src/<basename>` instead of inventing synthetic folders.
- Do not manually force compiler-generated sections or synthesize likely-generated init/ctor/dtor glue unless there is strong evidence.
- Keep code clean. No analysis debris, commented-out experiments, or notes in the project tree.
- Use other regions to inform understanding, not to override the EN target.
- Do not paste raw Dolphin addresses straight into config. Run `python tools/dolphin_sdk_symbols.py summary` first, use the inferred address translation, then review the surrounding translated cluster manually.

## Minimal Loop
1. Pick a target with real leverage.
2. Recover the surrounding dependency cluster, not just the leaf symbol.
3. Build the strict hash target with `python configure.py --matching` followed by `ninja` (if anything goes wrong, consider running `python configure.py -v GSAE01 --matching`). Always timeout ninja to 30s; plain non-matching `ninja` can stop at `main.dol` and miss the checksum target that CI runs.
4. Run objdiff and judge net progress.
5. If stuck, change angle quickly: adjacent code, data, assets, references, or tooling.
6. Commit real progress directly to the main working branch for this phase and push to main. No PR flow is required right now.
7. If the process changes (ie we expect ninja to pass after a session, etc.) please update this document.

## Commit Standard
Commit when both are true:
- The repo is materially better: codegen, data, linkage, structure, or tooling improved, even if marginally.
- The result is plausible original source or materially improves the ability to recover it.

Less process is intentional. Use judgment, move fast, and avoid spending an hour proving a bad assumption.

## Ghidra Drift Playbook (add-new-function pattern)

The Ghidra-imported `dll_XXX.c` files are usually decomp'd from a different snapshot
than the v1.0 `.s` we're matching against. Symptom: the report says "1 stub" but
when you read the asm you find 3-5 stubs, and the src functions (often named
`FUN_801cXXXX`) are at addresses INSIDE the asm symbol ranges - Ghidra split
them differently than the binary did.

**Do not** try to fix the misaligned `FUN_xxx` functions in place. Compile order
matters less than you think. Instead use the **add-new-function** pattern:

1. Read `build/GSAE01/asm/main/dll/<unit>.s` and list `^\.fn` symbols + sizes.
   That is the canonical function set.
2. List the src's current function set (`grep -nE "^void|^int|^undefined" src/...`).
3. For each asm symbol missing from src, ADD a new function with that exact name
   to the bottom of the src file. The src file's existing `FUN_xxx` functions
   can stay - they're dead/unreferenced or referenced only externally.
4. Read the asm body for the new function and translate to C. Cargo-cult call
   patterns from already-matched files:
   - `(*(code *)(*(int *)lbl_XXX + offset))(args)` for vtable calls through
     SDA21 pointer-to-pointer.
   - `extern int Obj_IsObjectAlive(void* obj);` style — check `src/main/dll/*.c`
     for existing extern declarations.
   - `(double)lbl_SDA_FLOAT` for f1 args to functions that take a `double`.
   - `*(unsigned char*)(p + off)` instead of `*(char*)(p + off)` when the asm
     reads with `lbz; cmplwi` (no `extsb.`).
5. Build, check the report, commit per-function gains. Even a partial fuzzy
   match (50-90%) is real progress and unblocks the unit from "0 of N matched"
   purgatory.

For deeper structural fixes (where the asm body of one symbol corresponds to a
`FUN_xxx` source function, just renamed), see `dbbc5ba9 Restructure laser19F`:
move the `FUN_xxx` body into the right symbol with the right signature, delete
the `FUN_xxx`, drop its `.h` declaration. Keep `FUN_xxx` only if other `.c`
files extern-reference it (e.g. shrine.c calls FUN_801c4b14).

Wins seen in practice: byte-exact matches when the body lines up well, ~50-99%
fuzzy otherwise. The remaining MWCC-vs-target diffs are usually unimportant
scheduling choices (`extsb.` vs `extsb + cmplwi`, `lis r3` order vs `slwi r0`,
epilog reordering of `lwz r0` vs saved-reg restores) that we can't trivially
force from C source.

## Forcing MWCC instruction selection: the asm{} + register trick

When a function reports 80-99% fuzzy and the diff shows MWCC picking a
"compressed" form where target uses a longer encoding, the residual is almost
always one of these instruction-selection mismatches:

| Source pattern | MWCC picks | Target picks |
|---|---|---|
| `x &= 0xfffffbff;` (single bit clear) | `rlwinm r0,r0,0,22,20` (1 instr) | `li r0,-1025; and r0,r3,r0` (2 instr) |
| `byte = (b & ~M) \| ((v & 1) << S);` | `andi.; ori` (constant collapse) | `li r3, v; rlwimi r0,r3,S,...` |
| `(s8)b != 0` (then branch) | `extsb.; beq` (fused, 1 instr) | `extsb r0,r8; cmpwi r0,0; beq` (2-3 instr) |
| `if (*(int*)p != 0)` | `cmpwi` (signed) | `cmplwi` (unsigned) — use `*(void**)p != NULL` |

`#pragma peephole off` + `#pragma scheduling off` around the function fixes the
`extsb./extsh.`/peephole cases (huge win — took dll_198 from low-90s to 100% on
3 of its 6 functions). It does **not** fix the rlwinm-vs-li-and or
andi+ori-vs-rlwimi cases — those are instruction-selection, not peephole.

For the instruction-selection ones, the only reliable workaround is an inline
`asm { }` block with `register` variables. The pattern:

```c
{
    register u32 m, v;        // m wins r0, v wins r3 (declaration order)
    register int pReg = p;    // forces parameter into a fixed register
    /* normal C statements that precede the bit-op stay outside the asm */
    asm {
        lwz v, 0x54(pReg)
        li m, -1025           // force the "long" form
        and m, v, m
        stw m, 0x54(pReg)
    }
    /* normal C resumes */
}
```

**Critical: declaration order chooses the register.** MWCC's allocator picks
volatile regs roughly in declaration order. Swap `register u32 m;` /
`register u32 v;` declarations to swap which goes to r0 vs r3 in the asm.
This is how `CameraModeCombat_free` and `fn_80189BE4` were taken from low-80s
to 100% — same source body, just reordered the two `register` lines until the
register assignment matched target.

For `rlwimi` (bit insert vs MWCC's andi+ori), the asm form is:

```c
{
    register u32 b;
    register u32 bitval;
    /* set bitval to the 0/1 value to insert */
    bitval = 1;
    asm {
        lbz b, 0x1d(t)
        rlwimi b, bitval, 5, 26, 26   // insert bit at position 5 (0x20)
        stb b, 0x1d(t)
    }
}
```

### When to NOT use this

- The diff is only scheduling reorder (mr/li swap, lfs/fmr swap before a call,
  prologue save order). `#pragma peephole off` is enough or there's nothing
  reasonable from source.
- The diff is missing or wrong function bodies (FUN_xxx vs target symbol).
  Restructure source first — asm tricks won't help if the function isn't even
  there.
- Function is below ~50% fuzzy. Solve the bulk of the body first, then come
  back for the asm{} polish.

### Cheap pre-check before reaching for asm{}

Try these one-line source rewrites first; sometimes they're enough on their own:
- `& 0xff7f` → `& ~0x80`         (often gives `rlwinm` where literal gives `andi`)
- `*(int*)p != 0` → `*(void**)p != NULL`   (signed → unsigned compare)
- `if (v <= K) return v; return K;` → `if (v > K) v = K; return v;`
  (gives `blelr` from a single value-clamp)
- For a constant reused across N consecutive stores, lift to a local:
  `f32 fz = lbl_xxx; *p1 = fz; *p2 = fz; *p3 = fz;` so MWCC CSEs across the
  stores instead of reloading.
- Swap declaration order of two `int` locals whose addresses are passed to an
  out-param function. The compiler allocates stack offsets in declaration
  order; if target wants `&objectIndex` at sp+8 and `&objectCount` at sp+0xc,
  declare objectCount first.
