Cross-referencing Route_S02 with UnitGroup_S02 gives per-phase membership: phase 1 has 37 squadrons and 42 members, phase 2 has 34 and 36, phase 3 has 44 and 49, with squadrons appearing in more than one phase so the totals overlap. The measured deployed count, stable across many runs and from the first observable instant, is 41 of 116 records. 41 against 42. The deployed set is the phase-1 roster, to within one record. That makes every previously puzzling observation coherent. All 116 records exist at mission load, confirmed on two stages, but only the current phase's squadrons hold craft. Deployed is 41 before the ready room ends and never moves because phase 1 deploys as a unit at load rather than on a schedule. No arrival was ever observed in up to 435 s of verified-live flight with 42 kills because phase 1 never completed in any run, and phases 2 and 3 hold the other 85 members. And the route times are within-phase fly-in timings rather than release times, which fits the frames reading where Stage 01 phase 2's 1500, 1800 and 2100 are 50, 60 and 70 seconds at 30 Hz. So enemies come into play per phase, deployed together at phase start, with each squadron's route giving its entrance path and the moment it flies in. The off-by-one is not explained and is recorded rather than rounded away. The player's record may be counted differently, one squadron may hold craft in a way the liveness read misses, or one phase-1 member may genuinely not deploy. A one-record gap is exactly the size of the errors that have misled this corpus before. What advances a phase is now the single remaining question. It is directly observable, since deployed would jump toward the phase-2 roster. It did not happen in 435 s of verified-live flight with 42 craft destroyed, so it is neither a short timer nor a small kill count. SUBObjectiveSettings names nine objectives but carries only rewards and HUD strings, no trigger. Not yet searched: StageMessageSet_S02.tbl, which has never resolved in the pak, and the executable, whose decrypted image exists only in guest RAM.
Reverse-engineering knowledge base
This directory is the spec-side of the clean-room: it records what the original Project Sylpheed binary does (behaviour) and how its data is laid out, so that the Rust port can be implemented from these specs without re-deriving anything and without ever copying original code.
It exists to answer one question fast: "do we already know how X works, and how sure are we?"
The one rule that matters
Never document a claim more confidently than the evidence supports, and never paste original code here.
A wrong-but-confident note is worse than no note: someone builds on it and the bug hides for weeks. Every entry therefore carries an explicit confidence and its evidence. This mirrors the project method — measure the oracle, never infer; refute before believing.
Clean-room firewall
- ✅ Allowed: behaviour descriptions, field offsets/types, formulas, state machines,
observed input→output pairs, and references to the original by address
(
sub_821B68C0) or toxenia-rs/sylpheed.db. - ❌ Forbidden here and in
crates/: pasted decompiled C/C++ or verbatim disassembled function bodies presented as the thing to reimplement. Cite the address; describe the behaviour in your own words. Disassembly is a tool for understanding, not a source to copy.
Confidence levels
| Level | Meaning | Bar to reach it |
|---|---|---|
CONFIRMED |
Behaviour verified against ground truth. | ≥2 independent observations or one observation cross-checked against an oracle (canary framebuffer, a known-correct value, a second code path). |
PROBABLE |
Strong single-source inference. | One clean observation, or an unambiguous static read of the disassembly. |
HYPOTHESIS |
Educated guess, not yet tested. | Anything else. Must say what would confirm/refute it. |
Promotion requires new evidence, not re-reading the old evidence. A HYPOTHESIS that
"looks right again" is still a HYPOTHESIS. Only an independent check promotes it.
If evidence later contradicts an entry, demote it and record the contradiction — do
not silently edit the conclusion.
When to document
- Right after a function/structure crosses from
HYPOTHESISto at leastPROBABLE— before moving to the next code path, so the knowledge isn't lost or re-derived. - Whenever confidence changes (up or down) — append to the Evidence log, don't overwrite.
- Not while it's still a pure guess with no evidence — a one-liner in the relevant backlog/plan is enough until there's something to stand on.
What to document
- Functions/code paths →
docs/re/functions/<name>.md(one file per function or tight cluster). - Data structures / formats →
docs/re/structures/<name>.md. - Keep the index in
INDEX.md(one line each: name · confidence · one-line summary).
Use the templates: _TEMPLATE.function.md,
_TEMPLATE.structure.md.
How we find and confirm code paths (the toolchain)
Everything joins on the guest virtual address (PC) — code addresses are fixed by the XEX load, identical across our emulator and canary.
- Static (cheap, try first):
xenia-rs/sylpheed.db(DuckDB: 25 481 functions, xrefs, strings, vtables, imports). Query withxenia-rs/zq.py—zq.py grep <str>,zq.py xref <addr>,zq.py dis <lo> <hi>,zq.py fn <pc>. Entry points are usually a string (zq.py grep MSG_DEMO) or an import (movie/XMA API) xref'd back to the loader. - Dynamic (when static is ambiguous): run
xenia-rswith its probe suite —--pc-probe/--audit-pc-probe-hex(fires at block entry),--mem-watch(mid-block reads/writes of a VA),--lr-trace(call/return chains),--trace-instructions,--dump-addr(read guest memory). These already exist; prefer them over hacking canary. - Oracle (correctness ground truth): canary — the Wine cross-build
xenia-canary/build-cross/bin/Windows/Debug/xenia_canary.exe(the native Linux ELF crashes / does not run — do not use it). This is the only emulator that reaches the in-game menu; ourxenia-rsnever got past the intro video. Use canary to observe output (capture its framebuffer for texture colours), not usually to instrument code — though itsbuild-crosstoolchain does compile, so small C++ probes + rebuild are possible when needed. Run muted, one emulator process at a time, point it at the real ISO (not the symlink).
⚠️ VA-equality caveat: join code by PC (fixed), but never assume a data VA holds the same bytes across emulators — allocators differ. Compare data by content/layout.