Find the cause of late-stage MIPS decompilation mismatches.
Real output — decomp-workbench diagnose-dumps on the phase-shift fixture in
this repository. From verdict to edit walks
this exact screen from top to bottom.
→ Read docs/START_HERE.md. Ten minutes, in order, with every command runnable right now against fixtures in this repository — no ROM, no compiler, no toolchain, no AI.
It answers the three questions people actually arrive with:
- Do I need to isolate the function so asm-processor (the community
preprocessor that lets hand-written MIPS assembly live inside C) stays
out of it? No.
Compare your normal full-TU build against the expected object;
--functionscopes it. Isolation changes codegen, so a harness is the wrong ground truth. - Do I need a permuter or an agent to use this? No. The verdict names the
mechanism, the
next:footer names the lever, and the field guide gives you the C. The permuter is optional, and it is a hypothesis generator rather than a solver. - Am I supposed to read
trace.lst? Not yet, and probably not at all. Traces are the last resort for one verdict class, and only if your project built an instrumented compiler.
Three pages are the entire workflow:
| Page | What it is for |
|---|---|
| Start here | One function, ten minutes: diagnose → lever → repeat |
| Field guide | "The diff looks like X" → the C that moves it, with the measured effect |
| Backlog walkthrough | Thirty near matches: batch triage, and which classes to knock out first |
Everything below is reference.
Install (Python 3.10 or newer, no runtime dependencies):
git clone https://github.com/akratch/n64-decomp-workbench.git
cd n64-decomp-workbench
python3 -m pip install -e .Compare two fixture dumps whose raw words differ only in relocated fields:
decomp-workbench compare-dumps \
examples/fixtures/target.objdump \
examples/fixtures/relocated-match.objdump \
--fail-on-mismatchverdict=instruction-exact aligned_total= 0 words= 0 raw= 2 norm= 0 ...
raw difference classes: relocation_controlled=2
next: Instruction-exact: raw differences are linker-controlled relocation fields ...
words=0 is the relocation-aware result. raw=2 shows why a literal word
comparison would have rejected the candidate.
Now diagnose a real residual—exactness and mechanism in one load, ending in a lever:
decomp-workbench diagnose-dumps \
examples/fixtures/phase-shift-target.objdump \
examples/fixtures/phase-shift-candidate.objdump \
--function animStepverdict: phase-shift structural=0 schedule=0 register=6 constant=0 hunks=1 playbook=temp-fifo-phase
signature: prefix-exact@12 state-divergence@temp:5 register-first-divergence
Six register differences, one upstream cause. Start here walks the rest of that screen.
Use it when:
- your candidate is close, but the remaining mismatch is hard to classify;
- you are compiling many source variants and need caching plus a durable ledger;
- the instruction shape matches but register allocation does not;
- you need to test whether uopt, ugen, or as1 (IDO's optimizer, code generator, and final assembler pass) owns a difference.
You do not need a ROM or compiler to try the included fixtures. Real object comparison needs a GNU-compatible MIPS objdump. Compiler tracing and pass replay need binaries supplied by your project.
Compare one function, out of your normal full-translation-unit build:
decomp-workbench compare target.o candidate.o \
--function function_name \
--objdump /path/to/mips64-elf-objdump \
--show-diffDiagnose exactness and the mechanism behind the residual in one invocation:
decomp-workbench diagnose target.o candidate.o \
--function function_name \
--objdump /path/to/mips64-elf-objdumpcompare and view remain composable primitives; diagnose loads each input
once and renders both truths together. Add --show-all for every hunk or
--html report.html for a self-contained handoff.
The --html report carries the same evidence — no scripts, no network, one
file you can attach to a PR or a Discord thread.
Run generated source variants through your existing compile wrapper. Each
{source} is a full translation unit, compiled the way your project compiles
one file:
decomp-workbench campaign target.o candidates/*.c \
--function function_name \
--objdump /path/to/mips64-elf-objdump \
--compile-command './compile-one.sh {source} -o {output}' \
--jobs 8The command template is tokenized and executed without a shell. Every ledger
record includes source, target, wrapper, objdump, explicit environment, timing,
and comparison identity. The campaign stops at the first exact match unless
--no-stop-on-exact asks for the whole grid, compares in process, and
terminates the compilers it started (and their children) if it is interrupted
or one exceeds the 120-second default --timeout.
The manifest and append-only ledger are created by default under
.decomp-workbench/campaigns/. Reopen the cockpit without rebuilding:
decomp-workbench campaign status
decomp-workbench campaign note "the padding macro's line layout is the active hypothesis"
decomp-workbench campaign resume
decomp-workbench campaign export --output campaign-report.htmlExternal generators can attach a validated family/parameter sidecar with
--experiment-manifest; selected instruction regions are ranked before the
whole-function residual. See candidate campaigns.
Package a single-function target, full context, and current source for manual decomp.me (the community's browser scratch and match-scoring service) creation without uploading anything:
decomp-workbench bundle-scratch scratch/demo \
--target-assembly target.s \
--context ctx.c \
--source candidate.c \
--platform n64 \
--compiler 'IDO 7.1 C++' \
--compiler-id ido7.1_c++ \
--language C++ \
--compiler-flags='-O2 -mips2' \
--diff-label demoDownloaded a decomp.me ZIP? Validate it and compare the site's own target and current objects before trying another source edit:
decomp-workbench doctor "/path/to/scratch.zip"
decomp-workbench check-scratch "/path/to/scratch.zip" \
--objdump /path/to/mips64-elf-objdump \
--show-diffcheck-scratch prints the browser score as context, then reports the
relocation-aware, LCS-aligned truth. With --compile-command, it composes
ctx.c, decomp.me's language-aware src.c/src.cxx line reset, and the
candidate source before compiling, eliminating a subtle source-line mismatch
that can change IDO's -g3 schedule. The report keeps preset, canonical
compiler ID, language, frontend, and expected driver distinct. See the export
tutorial.
Before publishing a proof or integration repository, check that every local dependency will actually travel:
decomp-workbench handoff audit /path/to/public-proof-repo \
--dependency-root /path/to/game-projectThis catches missing references and dependencies that exist locally but were never tracked. See public handoff audits.
Install the campaign skill for your preferred agent — optional, and it runs the same commands you would:
decomp-workbench install-skill codex
# or
decomp-workbench install-skill claude| What the comparison says | Next move |
|---|---|
structure-mismatch |
Keep working at the C/control-flow level |
constant-mismatch |
Audit the flag/enum against the assembly, then re-derive fakes |
commutative-order |
Change the expression tree (x |= y), not the allocator |
schedule-mismatch |
Regroup statements; use -g0 to locate ownership, not to prove the C |
allocation-mismatch |
Run view to name the family, then decomp-workbench guide <playbook> |
relocation-layout-mismatch |
Check relocation metadata, then the project link/ROM check |
exact=true |
Run the project’s normal collateral and full-output verification |
Whatever the verdict, --show-diff prints every differing site: no verdict
suppresses evidence. The field guide turns each of these
rows into the C that moves it, decomp-workbench guide <playbook|verdict|lever>
prints the relevant part of it in the terminal, and from verdict to
edit walks one case end to end.
| Problem | Command | Output |
|---|---|---|
| Are these objects instruction-exact? | compare |
Relocation-aware verdict, mismatch counts, register ranges, JSON |
| Can I share the comparison without sharing objects? | compare-dumps |
The same report from reduced objdump text |
| Can one command tell me exactness, mechanism, and the next lever? | diagnose, diagnose-dumps |
Comparison plus decisive aligned evidence, one input load |
| Where does the divergence begin, and which mechanism owns it? | view, view-dumps |
LCS-aligned hunks, register lanes, prefix signature, lever guidance |
| Is this machine ready, and is this scratch valid? | doctor |
Environment capabilities, handoff integrity, exact next command |
| Does this downloaded scratch really match? | check-scratch |
Browser score context, aligned object truth, optional site-faithful recompile |
| The footer named a playbook — what is it? | guide |
The field-guide levers for a playbook, verdict, or lever number |
| Which candidate is closest? | rank |
Stable structural and exact ranking |
| How do I run and reopen hundreds of variants safely? | campaign, campaign status/resume/export |
Parallel builds, cache, durable state, trajectory and HTML |
| How do I describe a generated family? | experiment validate |
Parameter/path/grid validation and selected-region contract |
| How do I manage the object cache? | cache status/prune/restore |
Dry-run cleanup and recoverable trash |
| What events are present in this trace? | trace-summary |
Event, register, and source-line counts |
| Is temp-register reuse following a FIFO? | trace-fifo |
Validated queue and physical-to-logical value schedule |
| Why did uopt keep or split a live range? | trace-globalcolor |
Per-web costs and color/split decisions, filterable by procedure |
| Which source/listing line owns a traced web? | trace-source |
Marker-aware correlation with ambiguity preserved |
| Can a measured allocator choice close the residual? | oracle plan/force/sweep |
Calibrated causal evidence, persistent status and export |
| Which alias facts reached uopt? | trace-alias |
Base provenance and may-alias decisions |
| Would one late-pass edit explain the object? | replay-as1 |
A rebuilt object from an edited retained listing |
| Can I hand this function to decomp.me without uploading it? | bundle-scratch |
Target, context, source, settings, and checksums |
| Will this proof repository work from a fresh clone? | handoff audit |
Missing paths, absolute paths, and untracked local dependencies |
| Can an agent follow the proven campaign method? | install-skill |
Portable Codex or Claude Code Agent Skill |
| Can I observe static-recompiled IDO? | instrument-ugen, instrument-uopt |
Instrumented generated C with opt-in traces |
On every command that selects one function — compare, compare-dumps,
diagnose, diagnose-dumps, view, view-dumps, check-scratch, rank,
compile-rank, campaign —
--symbol and --function are the same option, so either vocabulary works,
and passing both with different values is refused rather than silently
resolved. Every printed label is also the JSON key for that value;
decomp-workbench --explain-keys prints the one registry of comparison,
campaign, and aligned-view keys.
Run decomp-workbench commands for the compact journey map or
decomp-workbench completion bash|zsh|fish|powershell for a generated
completion script. Grouped spellings such as object diagnose,
campaign status, and trace source coexist with established flat commands.
The three narrative pages first, then the focused guides:
- Start here — an almost-matched function, end to end
- Field guide — the IDO codegen levers, with the C
- The
guidecommand — those levers, in the terminal - From verdict to edit — one screen to one source change, end to end
- Backlog walkthrough — thirty near matches, in triage order
- Workflow selection
- Object comparison
- Aligned mechanism view
- Candidate campaigns
- Calibrated allocator oracle
- External toolchains and calibration
- JSON and automation contracts
- Current product status and intentional boundaries
- Checking decomp.me exports
- Scratch bundles
- Public handoff audits
- Lessons from final-function campaigns
- Alternate authentic frontends — when the compiler itself is the variable
- Portable Codex and Claude Code skill
- IDO version support
- Trace analysis
- Compiler instrumentation
- Pass replay
- Tooling roadmap from live campaigns
- Elite product review and scoped backlog
- Castlevania 64 worked examples
- Troubleshooting
- Command design principles
The documentation index lists inputs, outputs, and support boundaries in one place.
Comparison, ranking, campaigns, scratch bundling, trace parsing, and pass replay are adapters: bring your own object files, objdump, compiler wrapper, scratch inputs, traces, or pass binaries. These workflows support IDO 5.3 and 7.1 when the project supplies the corresponding toolchain.
The packaged uopt patch profiles are intentionally narrower. They accept
generated uopt.c from one pinned IDO 5.3 static-recomp revision, verify its
SHA-256 and source anchors, and reject unknown input by default. The generic
ugen instrumenter supports a broader but shallower call/free-list trace.
The repository contains no ROMs, target objects, proprietary compiler binaries, or extracted non-code game assets. The attributed CV64 materials are limited to complete single-function scratch handoffs.
python3 -m pip install -e ".[dev]"
python3 -m unittest discover -s tests -v
ruff check src tests
ruff format --check src tests
mypy src testsSee CONTRIBUTING.md before adding a relocation type, instrumentation profile, or trace format.
CC0-1.0. Third-party tools and user-supplied inputs keep their own terms.
