* docs(M-102): fix findings-index false-positive, release <version> bug, land parked site cleanup
Docs triage pass (docs/dev/notes/PROMPT-docs-triage-session.md).
- gen_findings_index.py: D194's row was silently bucketed FIXED (an open
design item) because it had no distinct status cell, so the generator's
fallback keyword search matched "fixed" somewhere inside 6,500 chars of
narrative -- the same misleading-index failure mode that cost a session
on D227 (see docs/dev/findings.md SS H "D227 -- M-98"/"M-99"). Gave D194
a real status cell. Also extended LEAD_RE/bucket() with 5 verdict tokens
already in use in the doc (ANALYZED, GUARDED, DIAGNOSTIC SHIPPED,
ROOT-CAUSED, NOT A BUG) and added a leading verdict word to 4 more rows
that had a status cell but no recognized token -- fixes all 10 rows the
generator previously bucketed "?" (D175 D174 D173 D220 D160 D133 D136
D156 D153, plus D194). findings-index.csv regenerated; --check passes;
diff reviewed, only these 10 rows moved.
- ci.yml: release-notes.md's <version> placeholders were never substituted
before `gh release create --notes-file`, so every published release body
(including the live v0.1.0 release) ships the literal text. Now sed'd to
the tag first.
- Landed docs/dev/notes/UNLANDED-docs-site-cleanup-steps3-5.{md,patch}
(parked M-101, never pushed, branch deleted): deletes the orphaned
docs/StructureGuide.md (0 tracked references), adds title/description
frontmatter to 5 published Pages docs, rewrites docs/index.md's stale
"Phase 2 of 4 / no audio yet / 19-of-21" status text to match the
current README. The 2 files whose hunks no longer applied via `git am`
(docs/index.md, docs/dev/agentic-development.md) were redone by hand
against current main.
- docs/BACKLOG.md (local, gitignored): retargeted a dead
docs/PORT-LEARNINGS.md reference to docs/porting-notes.md.
Larger triage items (findings.md monster-row convention, full
status-accuracy sweep, duplicate-defect clustering, dev-notes plan
triage, HANDOFF rotation policy) scoped but not started -- see
docs/dev/notes/FOLLOWUP-docs-triage-session.md (gitignored).
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs/index.md: fix stale in-level-music claim before merge
This PR predates the M-102c/M-105 correction (already landed in the
README via PR #55): in-level music is NOT absent, it plays with some
tracks having wrong-sounding instruments / occasional garbling (D77
fixed, D230 filed for the residual). This branch's own rewritten Status
paragraph still said music 'doesn't yet' play -- fixing it to match
before merge so main doesn't briefly regress to a stale claim this
session already corrected elsewhere.
7.9 KiB
title, description
| title | description |
|---|---|
| Development Process | The investigation workflow behind the port — task budgets, file partitioning, and the finding-log discipline used across sessions and agents. |
How this port is developed
Most of this port's work is not writing code — it is diagnosis: figuring
out which N64 hardware or ABI behavior a piece of unmodified game code is
silently relying on, and satisfying it in the port/ layer without touching
the game's logic. A lot of that diagnosis is done with LLM coding agents, and
the workflow below is what makes that productive rather than chaotic.
For the project context — the two-model split, the timeline, and an honest
assessment of what did and did not work — see
dev/agentic-development.md.
It is adapted from Chris Lewis's write-up "Decompiling a Nintendo 64 Game in 84 Days" (Snowboard Kids, 2026), an AI-assisted decompilation that ran several times faster than its predecessor. That project bit-matches compiler output; this one matches runtime behavior, so the mechanics differ — but the project-structure lessons carry over directly.
1. Every investigation task gets a visible budget
An open-ended tool — for them the permuter, for us "rebuild and run the game and stare at a frame dump" — wrecks throughput when an agent will grind it indefinitely. Giving the agent a deadline it can see lets it trade tool-time against thinking and decide when to give up.
So every investigation brief states:
- a budget — "≤ N build → run → inspect cycles" or "~M minutes"; and
- the fallback on expiry: stop, revert any temporary probes, and write up what was found with an explicit confidence rating. A good write-up of a half-solved bug is a deliverable, not a failure.
The budget scales with the project's maturity. Early bugs are cheap (one-line truncations, off-by-ones) and get a tight cap; once those are gone the survivors are structural (matrix handedness, serialization formats) and get more room.
2. A shared, append-only learnings file
Lewis's agents recorded generalisable IDO quirks in a learnings file; later
agents read it and did better. Our equivalent is
porting-notes.md — the recurring N64→PC bug classes,
each entry a terse index into the full
dev/findings.md
log. Every investigation brief links it, and every investigation ends by
appending any new generalisable quirk.
The classes it currently tracks:
- pointer-width struct growth (32→64) in ROM-serialized or pun-allocated structs — the dominant class;
- 16-byte host
Gfx/Vtxvs 8-byte N64 — reservation and copy sizing; - big-endian rodata read on a little-endian host:
f32word-pairs, header offset tables, packed bitfields; - N64 hardware idioms the software RSP does not emulate (fill-rect Z clear, LOD/detail tiles, segment-address folds);
- whole serialized formats are converted by an offline sidecar rather than patched at runtime.
3. Parallelise by file, consolidate often
Lewis ran multiple git worktrees and found the real cost was synchronisation. For parallel investigation agents the same rule applies:
- Partition tracks by the files they will touch, so patches never collide. A task that can't be cleanly file-partitioned shouldn't be parallelised.
- Each agent reverts its own temporary probes before reporting, leaving only committed, env-gated, capped diagnostics — and never touches another track's uncommitted edits.
- One integrator merges each track with its write-up, then the next round starts from a clean tree. Investigation branches are not allowed to drift.
4. Spend the cheap automated pass before spending agent time
Before dispatching an investigation agent:
- confirm the build is green and the link is clean;
- run the existing env-gated probes for the area and attach their output;
- capture a baseline frame dump;
- grep the finding log for a prior instance of the same bug class;
- check the Perfect Dark port for the analogous code — same engine family.
Then hand the agent the findings, not just the problem.
5. What does not transfer from the decomp workflow
- The match-percentage loop. This port doesn't bit-match. "Done" is: no fault, and visually correct against N64 reference footage.
- "Understanding the function is the easy part." True when decompiling; inverted here — the hard part is identifying the implicit hardware/ABI contract the already-readable C depends on.
6. Keep PR/git-history noise low
A session produces a lot of small, independently-verifiable units of work — one Dxx row, one docs pass, one mechanical fix. Left unmanaged that turns into a pile of small open PRs that's harder to review than the work justifies. Rules:
- Squash-merge single-purpose and docs-only PRs. The repo has squash
merge enabled; use it (
gh pr merge --squash --delete-branch) for anything whose in-branch commit history (iteration, probe-add, probe-strip) isn't itself useful to keep — which is most PRs. Reserve a plain merge commit for a PR whose individual commits are each a distinct, reviewable unit someone might want togit revertindependently (e.g. this session's PR #50: a fix commit + a separate analysis-only commit). - Batch same-session, same-flavor docs updates into one PR/branch
instead of opening a new PR per finding row. A runtime re-check that
updates one
findings.mdrow (like #48) doesn't need its own PR if another docs-only branch is already open in the same session — fold it in. Only split into separate PRs when the pieces have genuinely different review/merge timing (e.g. one needs a human playtest, the other doesn't). - A branch takes only the change it's named for. Don't let unrelated housekeeping (a stray gitignore fix, a config tweak) ride along on an active feature/fix branch just because it's the one checked out — cut a separate branch, even for a one-line change.
- Stacked PRs declare the dependency in the title/body ("step 2 of N, step 1 is #N") and get rebased/merged in order promptly — don't let a later step sit open so long the earlier step's content drifts under it.
- Triage the open-PR list at least once a session, ordered by
time-to-close (docs-only and non-draft first, human-playtest-gated last).
A PR idle long enough that
mainhas moved past the finding rows it touches is stale, not just old — check for real textual conflict (git merge-tree), not just calendar age. Ifmainalready has more current information than the PR (a bug the PR still listsOPENthat's since been fixed and verified), close it rather than force a merge or spend a cycle rebasing it — re-derive only whatever part of its content is still true as a fresh, small PR against currentmain. Merging stale content back over newer information is a regression, not a save.
Investigation-brief template
TASK: <one bug, one subsystem>.
READ FIRST: docs/porting-notes.md; docs/dev/findings.md <specific Dxx entries>.
FILES YOU MAY TOUCH: <disjoint from any other in-flight work>.
KNOWN-GOOD / RULED OUT: <list — do not re-investigate>.
PRE-FLIGHT ATTACHED: <probe output, baseline capture, PD-port pointer>.
BUDGET: <N build->run cycles / ~M min>. On expiry: revert probes, write up with confidence.
CONSTRAINTS: no game-logic changes; ABI/layout/format only; #ifdef PORT; documented in the finding log.
VERIFY: <the exact tools_pc/verify.sh (or probe) invocation that proves this done — or, if runtime verification is impossible in this environment, say so and name what a human must run>.
REPORT: (a) root cause + file:line evidence (b) fix diff, or why not
(c) probes left in tree (d) confidence.
Append any generalisable quirk to docs/porting-notes.md.