This file is read by autonomous coding agents (the decomp-agents
orchestrator, ad-hoc Claude Code sessions, or any future tool) when
working on meteor-decomp. Humans should still read PLAN.md +
docs/matching-workflow.md first; this file just makes the
worklfow legible to an automated worker.
Each row of config/<binary>.yaml is one function. Pick one row,
claim it, match it, PR it. Do not try to "clean up" unrelated
functions, headers, or modules in the same session — other agents may be
holding adjacent work.
Claiming is GitHub-native, not a YAML edit. config/<binary>.yaml is
gitignored and regenerated by make split, so it cannot be a claim
ledger. Reserve a VA by posting /claim FUN_<va> <binary> on the
coordination issue and waiting for a granted/extended reply; the
deliverable is exactly one src/<binary>/_rosetta/FUN_<va>.cpp opened as
a PR into develop. Opening the PR pins the claim; merging it
auto-releases it. Full lifecycle in
docs/claim-protocol.md; fork-based checklist
in CONTRIBUTING.md. The local decomp-agents
orchestrator instead uses a SQLite claim queue inside one shared
checkout — that's the alternative coordination layer for in-process
parallelism (see ../decomp-agents/README.md).
make diff FUNC=<symbol> returns one of three verdicts (exit codes per
tools/compare.py's docstring):
- GREEN (exit 0) — every byte matches the original. The only acceptable commit state for matching decomps.
- PARTIAL (exit 1) — same length but some bytes differ. Not a commit.
- MISMATCH (exit 2) — different lengths / wholesale wrong. Not a commit.
If you cannot reach GREEN within the iteration budget, do not commit
a half-match. Revert your src/ edits, leave the YAML row alone, and
bail (append a post-mortem to decomp-notes/blocked/<binary>/<rva>_<sym>.md).
You CANNOT open Ghidra (*.gpr files are GUI-only). What you have:
- Asm dump:
asm/<binary>/<rva>_<symbol>.s— per-function x86 asm with seed-symbol labels. Generated bymake split BINARY=<X>.exe. - Headless pseudo-C (when present):
build/ghidra-decomp/<binary>/ <rva>_<symbol>.c— Ghidra's decompiler output, exported viamake decompile-headless BINARY=<X>.exe. Treat as a hint, not gospel. - Symbol metadata:
config/<binary>.symbols.json(rva, size, section, name). - RTTI catalog:
config/<binary>.rtti.json(vtable RVAs + class names — useful for identifying member functions). - Sibling matches:
src/<binary>/_rosetta/*.cpp— already-matched hand-written examples. Read 2-3 in the same module before writing your own. - Curator idioms:
decomp-notes/idioms/<binary>.md— binary-specific codegen patterns. - Prior post-mortems:
decomp-notes/blocked/<binary>/<rva>_<sym>.md— what previous workers tried on this same function. - Type catalog:
decomp-notes/types/<binary>/<rva>.md— discovered classes/structs from earlier matches. - Name overrides (collaborator-sourced):
config/<binary>.name_overrides.json— RVA → name for ~150 functions the binary still carries asFUN_xxx, recovered from the FFXIVLegacyClientStructs + ffxivDecomp collaborator repos. If the function you're matching is listed here, use that name. Cross-checked againstsymbols.jsonbut not byte-verified — confirm the role against the asm as you match. (Not folded intosymbols.jsonto avoid forcing a work-pool re-split; this is the override layer.) - Collaborator RE findings (used with the author's permission — see
NOTICE.md):../ffxivDecomp/docs/re/— opcode rosters, Lua-binding, inbound dispatch, cutscene, and receiver findings. Grep for your subsystem. Distilled indocs/ffxivdecomp_opcode_binding_map.md(outbound + bindings),docs/ffxivdecomp_inbound_opcodes.md(the ~70-entry server→client inbound roster), and (for the cutscene/kick path)docs/seq005_kick_gate_analysis.md.../FFXIVLegacyClientStructs/— struct field layouts. Catalog inconfig/<binary>.legacy_structs.json; per-class notes indecomp-notes/types/<binary>/; on-demand layout for any class viatools/analyze_legacy_struct.sh <Class>(runs against our own binary).
In a parallel-agent session, multiple workers are editing different files in different worktrees simultaneously. The discipline that keeps merges trivial:
- Touch exactly one new file:
src/<binary>/_rosetta/FUN_<va>.cpp(the fork/PR path), orsrc/<binary>/<module>/<symbol>.cppfor a curated hand-named match. One function, one file. - In the local
decomp-agentspath only, you may also flip your own row inconfig/<binary>.yaml(status+owner) — see "How a finished match is recorded" below. In the fork/PR path, leave the YAML untouched. - Do not add to shared headers (
include/<binary>/...) unless the addition is unambiguously correct AND no sibling-in-progress could be writing the same enum/struct. - Do not modify
include/_pending/unless that's the explicit staging area documented in PLAN.md (it isn't, as of 2026-05-18). - Do not edit
tools/,Makefile,PLAN.md,README.md,docs/, or any other coordination surface. Those are the curator's domain; raise a note instead.
The YAML row's type field tells you which workflow applies:
type: matching— the default, the byte-identical path described above. The_rosetta/source-of-truth, validated viaobjdiff.type: functional— semantic decomp validated by a test fixture rather than a byte diff. Usemake test FUNC=<symbol>instead ofmake diff. Used for code paths where MSVC 2005 lowering is too unstable to chase byte-by-byte (e.g. heavily templated CRT inlines).type: middleware-crt,type: middleware-miles— skip these. Vendor code, not in scope for clean-room.
If your row is middleware-*, release the claim immediately with
outcome skipped and pick another. Don't try to match vendor code.
When make diff returns PARTIAL/MISMATCH, the most common root causes
in MSVC 2005 frequency order:
| Symptom | Fix |
|---|---|
| Wrong register allocation | Reorder local declarations (MSVC allocates in source order) |
| Off-by-one stack frame | Add a dead local of the right type |
| Branch direction flipped | Negate the if-condition |
__stdcall / __cdecl mismatch |
Check ret N in epilogue |
Member fn looks __cdecl |
Should be __thiscall |
| FP code mismatched | MSVC 2005 uses x87, not SSE2 |
if (a && b) vs if (a) if (b) |
Try the other lowering |
for vs while |
Same body, different prologue |
| Switch jump table | MSVC builds at ≥4 cases |
| String literal positions | /GF pooling — try __declspec(selectany) |
__security_cookie dropouts |
/GS triggers on local arrays ≥5 bytes |
| Tail call missing | Use __forceinline or if(...) return f(); |
| Element-wide ptrs vs index loops | MSVC-2005 idiom: *p++ over arr[i++] |
See docs/matching-workflow.md §7 for the full table with examples.
The durable record of a solved function is the committed
src/<binary>/_rosetta/FUN_<va>.cpp file itself — the _rosetta tree IS
the solved set. There is no status: matched flag to flip as the
source of truth, and the YAML work pool's status: column is no longer
maintained.
- Fork-based / PR path (the GitHub-native flow in
CONTRIBUTING.md): your branch adds exactly one_rosetta/FUN_<va>.cpp. Do not editconfig/<binary>.yamlat all — opening the PR pins the claim and merging it auto-recomputes progress (reconcile.yml). Keeping the diff to one file is what makes the PR trivially reviewable. - Local
decomp-agentspath: the SQLite queue inside the shared checkout is the claim authority; that orchestrator still flips the function's YAML row (status+owner) as a local bookkeeping side effect. If you are that worker and you DO edit the row, change ONLYstatus+owner— neverrva,end,size,module,symbol,type,section; do not reorder rows, strip trailing whitespace, or "normalize" quoting. Every change beyond those two makes the merge harder.
- Read
docs/matching-workflow.mdend to end — it's the canonical guide. - Read 2-3 sibling matched files in the same module before writing.
- The Project Meteor Discord dump
(
/Users/swstegall/.claude/projects/-Users-swstegall-Documents-Programming-server-workspace/project_meteor_discord_context.md) has first-hand contributor notes on MSVC 2005 quirks — grep for symbol names that look like they'd be familiar to the Meteor team.