`slb_leading_segment_disc` was SIGKILLed by the OOM killer in `docker/ci/run`
at its 7 GB cap, so the documented 45/377 baseline did not reproduce on a 15 GB
box.
`PakArchive` holds the whole concatenated payload in memory and `sound.pak` is
1.01 GB (sound.p00-.p04). `bank()` and `bank_named()` opened it on every call --
inside loops -- and five tests opened their own besides, ~26 opens in all. With
cargo's default thread count that is ~6.1 GB of archive in flight against a
7 GB cap with `--memory-swap` equal to `--memory`, so there is no swap to
absorb it.
The archive is immutable once open and every accessor takes `&self`, so one
`OnceLock` instance is equivalent to N private ones at 1/N the memory.
⚠️ The failure mode is worth knowing: a SIGKILLed suite prints no
`test result:` line at all, so it disappears from a scraped tally instead of
failing visibly. The run still reported "0 failed" -- true, and useless. Check
cargo's exit code (101), not the tally.
Measured in the capped container, 7 GB, default threads:
* before: SIGKILL (signal 9), 0 of 10 tests reported
* after : 10 passed in 3.17s
* (single-threaded before the fix: 10 passed in 22.64s -- the fix is also
~7x faster, because it no longer re-reads 1 GB from disc 26 times)
* cargo fmt --check clean; cargo clippy --tests -D warnings clean
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Project Sylpheed ships as a retail XEX2 (human decision, 2026-09-16), so
Phase 2's key harvest is closed rather than left looking unfinished.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Category pages linked each family as `<slug>.md`, relative to categories/,
where no family page lives. They now link `../<category>/<slug>.md`.
- Form pages linked a member into its *own* category directory, so every
VMX128 sibling (`vsldoi128`) pointed at vmx128/ although its family page is
under vmx/. They now link into the family's directory.
- Hand-written "Related" and sibling mentions linked other categories' pages
as if they were in the same directory. 109 are retargeted through the page
index; 29 that pointed a family page at itself (`vrefp128` on vrefp.md) and
6 naming instructions the manual has no page for are plain text now.
Regenerated at the existing Canary pin (f21ebd49e): upstream has moved on, and
re-pinning belongs in its own change. The generator reports 0 family pages
changed and is idempotent; the only dead links left are TEMPLATE.md's
placeholders.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record the end state at the top of the page: four repositories archived, their
clones gone from this machine, the Phase 7 drops done or never present here, and
Canary's default branch moved off `xenia-rs`. The retention question and the
history rewrite stay open for the human.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing here changes what a tool computes; it changes where tools look.
- tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has
existed nowhere since /work became a clone, so they matched nothing and
printed empty results. They now resolve the disc through a new disc.py
from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised).
Nine scripts that imported siblings from the retired Reborn checkout or an
old session scratchpad now import from their own directory. unitgroup.py
only needs the variable when --pak is not given.
- sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead
devkit key and a doc comment claiming a devkit fallback that does not
exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either.
- sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so
always skipped. It reads $SYLPHEED_DISC now, and passes against the disc.
- Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe
as places to look now name sylpheed.db, Canary's ppc_context.h and the
flat .pe; docs/re/README.md no longer says the native Canary build does not
run.
Historical records keep their original paths: findings that were measured
against /work/xenia-rs/sylpheed.db still say so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three untracked files in the project root had no home in either repository:
- the Xbox 360 technical reference report (a research compilation) becomes
docs/reference/xbox360-re-technical-reference.md, unchanged;
- XBOX360_ARCHITECTURE.md becomes docs/reference/xbox360-architecture.md,
trimmed to its platform facts. Its format "status" sections (PAK unknown,
mesh unknown, audio TODO) and the `just sniff` workflow predate every
decoder in this repo and were wrong;
- generate_export_docs.py becomes tools/generate_export_docs.py, the path the
committed xbox360-exports.* already name as their generator. It lived
inside a Canary checkout, so it now takes --canary and --out and fails
loudly on a tree that is not Canary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hand-written parts of the manual still described how the retired
xenia-rs interpreter behaved: its snapshots, Rust casts and helpers. Each of
those 490 statements is now either restated as what Canary's emitters and
x64 backend actually do (at the pinned canary_experimental commit), or
dropped where it only made sense for xenia-rs.
Checking them turned up claims that were wrong, not just outdated:
- VSCR[SAT] is never modelled in Canary (DID_SATURATE is a stub and mfvscr
cannot see it); the pages said saturating ops set it stickily.
- Canary does not implement lswi/lswx/stswi/stswx, dcbi, mtfsb0/mtfsb1,
vmsum*, vmhaddshs, vupkhpx/vupklpx, and most SPRs; pages described them
as working.
- Traps evaluate TO in Canary; stvebx/stvehx/stvewx store one element, not
16 bytes; mtmsrd writes only EE; fres/frsqrte/vrsqrtefp precision claims
and the stfs "rounds under RN / sets FPSCR" claim contradicted the spec.
- Reservations are a 64 KiB block bitmap plus a value compare, not
per-address tracking.
Claims that neither Canary's source nor a public spec settles are marked
unverified (NI at boot, vmaddcfp128 operand order, estimate bit-exactness).
Generated regions are untouched; re-running the generator changes nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Its data-table section globbed `/work/sylph_extract/**/*.pak`, a path that has
existed nowhere since `/work` became a clone. The glob matched nothing, so the
section reported "0 disc names" and an empty table with no error — while the
committed `docs/re/data/name-block-bases.txt`, generated back when the path
existed, records 13 450. The disc is required for that section, so a missing or
non-disc `$SYLPHEED_DISC` is now an error (exit 1), not an empty result.
Verified: without the disc, exit 1 with the message; with
`SYLPHEED_DISC=…/sylph_extract` (the original /work/sylph_extract), the section
reproduces the committed figures exactly — 13 450 distinct disc names,
50 / 261 data-table rows, `sub_82341A20 r30 0x82088F94 217 names 89% disc`.
⚠️ The artefact itself is deliberately NOT regenerated. Rebuilt from the current
database it changes findings — solved bases move, three 15/15 rows vanish, −280
lines — and the cause is the database, not this script: the string extractor's
Shift-JIS path requires kana, so kanji-only Japanese (e.g. the allocator's
`32B 未開放 %4d : 最大…使用量 %4d / %4d` debug lines) is not in `strings`, and
the bases those names pinned no longer resolve (sub_8285F2C8 r5 @ 0x820AE7AC:
15/15 before, 7 now). Regenerating would bake that regression into a cited
research artefact (`docs/re/structures/player-tuning-tables.md`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Instructions and generated text that still sent readers to `xenia-rs`, which is
archived and deleted locally. Provenance and dated research records are left as
they are — "lifted from xenia-rs", or a finding stating what database it was
measured against, is still true, and rewriting it would falsify the record.
Changed, because each one tells someone what to do today or writes stale text:
docs/re/README.md the RE toolchain guide queried `xenia-rs/sylpheed.db`
with `xenia-rs/zq.py`, and its "Dynamic" bullet said
to prefer xenia-rs's probe suite OVER Canary. Now:
`sylpheed.db` at the repo root (how to build it and
re-stamp names), `tools/zq.py`, dynamic = Canary, and
`.rdata` reads from the `.pe` (offset = VA - 0x82000000)
in place of `--dump-addr`.
challenge-mission-gate.md, structures/achievements.md
reproduction commands `python3 xenia-rs/zq.py dis …`
crates/sylpheed-xexdb/SCHEMA.md
titled after the retired `xenia-analysis` crate,
citing `xenia-rs dis`, pointing at a `xenia-analysis`
source path
sylpheed-xexdb/src/formatter.rs
WROTE "generated by xenia-rs" into every disassembly
it produced (no test pins the banner)
sylph-xexdb.rs, db.rs "SQLite" / "DuckDB writer for xenia-rs"
sylpheed-formats/src/hash.rs
pointed at `xenia-rs/RE_SYMBOLS.md`, now at
`docs/re/RE_SYMBOLS.md`
Verified: every rewritten reproduction command was extracted from the doc and
run as written against the regenerated database — all 5 exit 0 and return the
disassembly they describe. The README's function count (25 676) is the database's
own. `cargo fmt --all -- --check` clean.
Not changed, deliberately: the README's Oracle bullet says Canary's native Linux
ELF "crashes / does not run". That is about Canary, not xenia-rs, and it conflicts
with a July note that the native build works — unverified either way here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The generator had not been able to run correctly since the manual moved into
`tools/ppc-manual/`: it computed the repository root as `HERE.parent.parent`,
which now names `tools/`, so the XML, Canary's emitters and xenia-rs all stopped
resolving — silently, because both scrapers skipped what they could not find.
Every page's references had been pointing at paths that exist nowhere.
What each source contributed, measured on the 350 pages before this change:
Operation (pseudocode) 251 pages: fixed boilerplate "derives from the xenia-rs
interpreter"; 99 carry real hand-written seeds
C translation 337 pages: the same kind of boilerplate
xenia-rs snapshot 336 pages: the interpreter arm, pasted in — the only
per-instruction semantics on unseeded pages
links xenia-rs opcode/decoder/interpreter + Canary emitter
Now:
* semantics come from **Xenia Canary**, the reference emulator, read through
`git show` at a pinned upstream commit (`origin/canary_experimental`,
f21ebd49e9). Not our checkout: it carries instrumentation and lacked
upstream's `mcrf` fix, so it would have published probes and a wrong `mcrf`.
Each page embeds the emitter (`InstrEmit_<mnem>`), and for the 128 pure
one-line delegations also the helper that holds the semantics.
* decode references point at `crates/sylpheed-ppc` — the decoder that
produces `sylpheed.db` — as in-repo relative links.
* the boilerplate now says what is true, and the C translation guide maps
Canary's actual HIR calls, checked against `ppc_hir_builder.h` (including
that `UpdateCR(n, v)` truncates to 32 bits).
* `rust_scraper.py` -> `decoder_scraper.py` (interpreter half dropped);
missing sources are now errors, not empty results.
Verified:
consistency checks 455 XML entries, 350 families, 598 index keys
hand-written tails 386/386 byte-identical after regeneration
xenia-rs in generated 0
pages with a snapshot 349/350 (was 336) — `dcbi` has no Canary emitter at all
in-repo decoder links 910/910 resolve to a line holding the identifier
emitter boundaries brace counter == column-0 `}` rule on 521/521;
preprocessor model unit-tested (#if 0/#else/#elif)
idempotency re-run: 0 pages updated, 0 working-tree changes
Hand-written notes (outside the generated regions) are not rewritten here:
* 110 links into `../../xenia-rs/...` were dead; they now point at the file in
the archived repository (git.mc02.dev/fabi/xenia-rs @ 8401d4d). Line anchors
were dropped because the notes predate that commit — 0 of 441 old line
ranges match it — and a precise-looking wrong anchor is worse than none. The
link text, which carries the author's line numbers, is unchanged.
* 140 prose claims about xenia-rs's behaviour remain. 23 are verified to hold
for Canary too (the 32-bit CR0 truncation, OE left unimplemented); the other
114 need checking one by one, and some invert — e.g. `divdx` notes a correct
64-bit CR0 update in xenia-rs where Canary's `UpdateCR` truncates. Left for
a deliberate pass rather than a blind substitution.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`SYLPHEED_DB` is the documented contract for the static-analysis database —
`docker/decoder/sylph-decoder` sets it, and `docs/agents/CONTAINER-NOTES.md` and
`decoder-loop.md` list it — but no reader honoured it:
isl_cmdtab.py, name_block_bases.py hardcoded `/work/xenia-rs/sylpheed.db`, a
path that has not existed anywhere since
`/work` became a clone. Both failed on
every machine, before and after the
database moved.
zq.py used `$SYLPH_XEXDB`, a name invented in
#39 without grepping for the existing one.
All three now resolve `$SYLPHEED_DB`, then `$SYLPH_XEXDB` (kept as an alias so
nothing already written against it breaks), then `<repo root>/sylpheed.db`, and
refuse with exit 1 naming both variables and the build command otherwise.
`grab_tutorial.sh` hardcoded its helpers into the retired `sylpheed-reborn`
checkout. That copy's `skip_intro.sh` still calls the removed `vgamepad` and
exits 0 having pressed nothing — the failure this repository's own copy was
rewritten to make loud. So the script was already running a silently broken
helper; it now resolves its helpers from its own directory. Nothing exists only
in reborn's `tools/re-capture` (checked: 0 reborn-only files).
Verified against the same database, output compared byte for byte with the
ORIGINAL scripts (path-substituted copies, sibling imports resolvable):
resolution path isl_cmdtab name_block_bases
default (root) identical identical
$SYLPHEED_DB identical identical
$SYLPH_XEXDB identical identical
bad path exit 1 exit 1 (zq.py: exit 1 too)
and `isl_cmdtab`'s output is byte-identical to the body of the committed
`docs/re/data/isl-command-table.txt`.
⚠️ `name_block_bases`'s output does NOT reproduce the committed
`docs/re/data/name-block-bases.txt` (2,609 lines differ, `strings in the image:
7366` vs `7140`). That is the database, not this change: the artefact was
generated from the agent box's older 586 MB database, and this machine's is the
current generator's. Two databases are in circulation. Not regenerated here.
⚠️ Also not fixed, because it is a different bug: `name_block_bases.py` globs
`/work/sylph_extract/**/*.pak`, another path that exists nowhere now; that part
of its report is silently empty. It should read `$SYLPHEED_DISC`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`the_derived_order_puts_primitives_last_and_that_wipes_screens` asserted that
switching primitives on still flattened at least one screen (`wiped_on > 0`),
and said to turn `include_primitives` on by default the day it stopped.
`cfcda55` made that day arrive — a keyless primitive that would hide the screen
is now forced to paint first — and the wire had been red since 2026-08-29,
invisible to CI because the runner has no corpus and the suite self-skips.
#38 chose option A: keep the default off, and pin the fix instead. The flatness
proxy stood in for "paint order is solved", and the fix's own record says that
is only partly true — "This does **not** make `include_primitives` safe by
default" (docs/re/structures/ui-prm-primitives.md). So:
* renamed to `no_build_is_wiped_with_primitives_on` — the old name asserted
the bug; it survives in the doc comment for anyone searching from #38;
* `wiped_on > 0` -> `assert_eq!(wiped_on, 0)`, with a message saying what a
failure now means (`forced_backdrop` regressed) and that it must not be
"fixed" by turning primitives off in the test;
* `include_primitives` stays `false`; no library code changes.
It is the only end-to-end check of the rule: the four tests in
`ui_forced_backdrop_disc.rs` pin its mechanics, and none composes with
primitives on.
Verified with the corpus present, including that it can fail:
fix 3 passed 125 builds draw a primitive, 0 wiped
forced_backdrop disabled FAILED 36 of 125 wiped — the new message fired
restored 3 passed 125 builds, 0 wiped
The control's 36 is exactly the "36 builds ... wiped by our own sort" that
`cfcda55` and `ui-forced-backdrop.md` report — the guard independently
reproduces the fix's own number, so it measures precisely what was repaired.
The control was a one-line `return false;` in a scratch copy; the working tree
was restored and `git diff` showed only this test file before committing.
Closes#38
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 3 lifted the DuckDB tool out of `xenia-rs` but not the two files that go
with it, and this branch's own code already depends on one of them: the FK rule
in `db.rs` and `db_schema_golden.rs` exists *because* `apply_re_symbols.sql`
re-stamps reverse-engineered names onto `functions` after every regeneration.
Sylpheed referenced that file three times and contained it nowhere — on no
branch. CONSOLIDATION.md's end state is "Sylpheed holds everything", and the
`xenia-rs` clone is scheduled for deletion.
docs/re/RE_SYMBOLS.md byte-identical to xenia-rs/RE_SYMBOLS.md (cmp)
tools/apply_re_symbols.sql every statement identical; the two comment lines
that name paths now name this repo's paths
Verified against the real 336 MB database, which carries all 16 foreign keys,
inside a transaction that was rolled back (the database is unchanged):
every statement runs, and 208 functions carry reverse-engineered names. Run
from both the old location and this one.
Not harvested: `xenia-rs/zq_dis.py`, an untracked six-line subset of
`zq.py dis` with a hardcoded path — superseded, nothing to keep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit on this branch made `tools/zq.py` look for its database at
`<repo root>/sylpheed.db`, and its refusal message tells people to put one there.
Nothing ignored that path. The database is a build artefact of several hundred
MB, so a single `git add -A` would have committed it into a public repository's
history — the class of blob CONSOLIDATION.md's history-rewrite fork exists to
argue about.
Anchored (`/sylpheed.db`, plus DuckDB's `.wal`) so it covers only the default
location, not any file of that name elsewhere. Verified by creating the files:
both ignored, `git status` silent, and a `sylpheed.db` in a subdirectory is not
swallowed by the rule.
Found while writing the Phase 7 steps: moving the 336 MB database out of
`xenia-rs` before that clone is deleted is exactly how someone would put it here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the page's one explicitly outstanding result. Run in sylph-ci:local
(rustc 1.98.1, byte-identical to the runner) with the disc present and
--no-fail-fast: 45 suites, 367 passed, 1 failed, 14 ignored. The +9 against the
disc-less #35 run (377) is exactly #35's nine imports.rs unit tests.
The single failure is ui_prm_primitives_disc, red on main since #23 and
bisected there, not introduced by #32.
Records the three things that stopped the number being obtained, as rules:
--no-fail-fast (an earlier run silently covered half the workspace), a memory
cap sized to the host (the default 7 GB OOM-kills slb_leading_segment_disc on
this machine with the disc mounted), and a ~36 GB disk budget for a cold target
volume.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re-checked against the server rather than the page. `Sylpheed` has 7 tags and
all 7 are `formats-pin-*`. The eighth this page counts, `formats-pin-2026-08-29`
without a letter suffix, is in `Syplheed-Reborn` — a repo a Sylpheed rewrite
would not touch, and one Phase 5 archives read-only regardless.
The pin itself is exactly as claimed: `formats-pin-2026-09-01` resolves to
`e2630413`, and the in-container build fetches that git dependency over the
network for real, so the blocker is live rather than theoretical.
Also updates the disk figures the "don't rewrite" recommendation rests on: 90%
used and 96 GB free, not 83% and 154 GB, with `target/` at 34 GB. The
recommendation is unchanged and the argument is now stronger — reclaiming
118 MB by rewriting history while 34 GB of rebuildable output sits beside it is
the wrong lever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`check-capture-citations` exits 1 on a clean checkout of this branch, and would
have kept doing so for ever:
🔴 cited but NOT committed: 1
docs/re/captures/no-such-file-anywhere.png
`SEARCH` includes `tools`, so the scan reads this file -- and `selftest()`'s
planted fixture is a literal capture path. The checker cited its own test data,
to a file that will never exist by construction. A gate that is red before
anyone changes anything teaches people to ignore it, which is the opposite of
what Phase 4 built it for.
Excluding only this one file (`SELF`) keeps every other tool in scope. A real
citation belongs in a page, or in a tool that opens the capture -- never in the
checker itself.
Added a selftest case for it, because this is exactly the class of bug the rest
of this file already documents surviving: the ERE that matched nothing while
five ticks stayed green. Verified the guard can fail, rather than assuming it:
exclusion neutralised -> "own fixtures not counted 🔴 FAILED",
selftest rc=2, real check rc=1 reproducing the bug
exclusion restored -> selftest ok (7 cases), check rc=0
After the fix, on this branch:
captures committed : 212
cited by a page or a tool : 212
cited by nothing : 0
🔴 cited but NOT committed: 0 rc=0
which is also the first independent confirmation of Phase 4's "212 captures,
all cited" -- the checker now agrees with the page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This page says not to trust its own ledger on arrival, so the checks were run
again rather than read. Nothing was archived and nothing was deleted — both are
the human's, and both phases stay open.
The substantive finding is that Phase 5's containment check is the wrong test
for half the list it names. `xenia-rs` and `xex2tractor` were never absorbed;
their disposition is harvest-then-archive. Run the check literally against
`xenia-rs` and it returns 42 of 42 refs "not present in Sylpheed" — every line
true, every line unactionable, which is the failure this page already names
once about Phase 0's first gate. For those two the invariant is that the
harvested artefacts are in Sylpheed and the repository is archived rather than
deleted; that is now checked separately, and it shows the harvest is real but
lives only on the unmerged PR branches, so Phase 5 should follow them rather
than precede them.
Containment itself holds for the two repos it does apply to: 21/21 and 2/2.
Also answers Phase 7's one open question — nothing cites `texcompare/`, and all
four `ship_render` citations name the example program, not the output
directory — and records that most of Phase 7's list does not exist on this
machine, that `has_wiki: true` is a flag rather than content (all six wikis
return 404, never initialised), and that the disk is at 90%, not the 83% the
page records.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This page recorded it as "not changed here, because the right replacement is a
decision". The decision was taken: PR #35 `38cc170` resolves `$SYLPH_XEXDB`,
then `<repo root>/sylpheed.db`, then refuses with both named — no third
fallback, because the database is an untracked build artefact and a default
that silently resolves to the wrong one is worse than no default.
Leaving the page saying otherwise would be the defect this section is about.
`SCHEMA.md`'s retired-repo heading is still outstanding and stays listed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`zq.py` hardcoded one absolute path, and it pointed inside `xenia-rs` --
a repository CONSOLIDATION.md archives in Phase 5 and drops in Phase 7. It
resolved on exactly one machine, and on that machine it was days from becoming
a `duckdb` exception about a missing file, with nothing saying why.
Resolution order is now `$SYLPH_XEXDB`, else `<repo root>/sylpheed.db`, else a
refusal that names both. The repo-root path is taken relative to this script,
not the caller's cwd, because zq.py is run from wherever the investigation is.
No fallback beyond that, on purpose. The database is a build artefact of a few
hundred MB and is not tracked, so there is nothing to fall back *to*; a default
that silently resolves to the wrong database is worse than no default. That is
issue #16's lesson, which was about exactly this shape in the test suite.
Both failure paths say what to do and exit 1:
$ zq.py fn 82000000 # nothing set, nothing at the repo root
no database found.
looked for: /…/Sylpheed/sylpheed.db
set $SYLPH_XEXDB to an existing one, or build it with:
sylph-xexdb dis <xex|iso> --db sylpheed.db --analyze sql
$ SYLPH_XEXDB=/nope/missing.db zq.py fn 82000000
$SYLPH_XEXDB is set but is not a file:
/nope/missing.db
Also fixes the regeneration hint, which still named `xenia-rs dis` -- the
retired binary. It is `sylph-xexdb dis`.
Connecting is skipped when there is no subcommand, so `zq.py` still prints its
usage on a machine that has no database yet.
Verified: usage with no db (rc 0); missing db (rc 1); `$SYLPH_XEXDB` set to a
non-file (rc 1); and `$SYLPH_XEXDB` pointed at a real 336 MB database, where
`imp Rtl` returns its 1,280 `RtlLeaveCriticalSection` call sites. That database
turns out to have been generated from the uncommitted tree this branch ports:
it carries `import_address`, `import_role` and all 16 foreign keys -- an
independent confirmation of the schema the golden test now locks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found 2026-09-14 while pulling the repos on the other machine: `xenia-rs` had
468 uncommitted lines plus an untracked 338-line `imports.rs` in the very crate
Phase 3 lifted, written 2026-09-10 and sitting there for four days. Verified
absent from `iterate-4A` and from the lifted crate in PR #32 — it existed in
exactly one place, and that place was not git.
Phase 0 is titled "Secure the single-copy work" and its gate passed anyway,
because the gate iterates `refs/heads` and a dirty working tree has no ref.
Canary's two dirty files were secured only because a human already knew about
them; nothing found them. Phase 1 then declared the archive honest while this
sat beside the branches it pushed. Phase 5 ends with "delete the local clone"
and Phase 7 drops the emulator, so a deletion was scheduled against it.
Records: the finding and the `git status --porcelain` half the gate was
missing (run across all four repos — only Canary is dirty, and both entries are
already on Phase 7's drop list); that it is secured on `harvest/import-thunk-
naming` and ported in `feat/xexdb-import-naming`; a fifth entry in the
wrong-claims list; and a `0b` row in the status table.
Also records three pieces of Phase 3 residue found while porting, the sharpest
being that `tools/zq.py` still defaults its database path to a location inside
`xenia-rs` — the repository Phase 5 archives and Phase 7 drops. Left unchanged:
the right replacement is a decision, not a guess.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Carries `xenia-rs` `harvest/import-thunk-naming` (b4f19f1) across into the
lifted crate. That work was found UNCOMMITTED in the retired repo's working
tree on 2026-09-14 and exists nowhere else: absent from `iterate-4A`, absent
from this crate as lifted. CONSOLIDATION.md Phase 5 ends with "delete the local
clone" and Phase 7 drops the emulator, so it had a deletion scheduled against
it.
Not a cherry-pick. The lift's base is exactly the harvest's parent (8401d4d),
so each file was merged three-way -- lifted vs base vs harvest -- which is what
made the port reviewable: 10 conflicts, 9 of them pure rustfmt reflow from the
lift's formatting pass, and 1 semantic.
The semantic one is insertion ORDER. `xdbf_achievements.image_id` is now a
foreign key onto `xdbf_images(id)`, so the image rows must be inserted before
the achievement rows. The merge moved that block; the conflict was the stale
copy left at the old position.
What arrives:
* **Import-thunk recognition** (`imports.rs`, 338 lines). An XEX import is not
a PLT jump: the linker emits a four-word thunk whose first two words are
import RECORDS that the loader rewrites at module load. On disc they are
still records, so a PowerPC-only decoder prints two meaningless `.long`s in
front of an indirect branch. This maps every word of every thunk, and every
direct branch into one, back to its `imports` row. Shape-validated rather
than trusted: an entry is indexed only when the four words it points at
actually have the thunk shape.
Adds `instructions.import_address` (FK onto `imports.address`) and
`import_role` (`'record'` | `'thunk'` | `'call'`, NULL iff import_address is
NULL), plus an index. `tools/zq.py` gains `imp` and `impcalls`, and `dis`
now names imports instead of printing `.long 0x01010194`.
* **Sixteen schema-wide foreign keys**, declared wherever a column is derived
from another table. CREATE TABLE and insertion order become load-bearing.
`functions` is deliberately NOT an FK parent and the golden test now asserts
zero inbound FKs onto it: DuckDB implements UPDATE as delete+insert, so one
inbound FK would make `functions.name` un-updatable and break
`apply_re_symbols.sql`, which re-applies RE symbol names after every
regeneration.
The database path needed no change in the binary: `DbWriter` builds its own
index inside `ingest_instructions` from `info.import_libraries`. Only the two
output paths (`enrich_section` for JSONL, `write_asm`) take it as an argument.
Verified: `cargo test -p sylpheed-xexdb` = 10 passed / 0 failed, including
`db_schema_golden` (41s, builds a real DuckDB) which locks the 16-FK set and
the no-FK-onto-functions rule. `cargo clippy -p sylpheed-xexdb --all-targets
-- -D warnings` clean; `cargo fmt --all --check` clean.
Stacked on #32 -- it ports into a crate that only exists there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paused at six of eight phases, all committed and pushed, nothing left in a
working tree. The page now carries a STATUS section the other machine can
finish from: what is done, which PR each phase is in, what to run next, and
what is deliberately left for the human.
🔴 FOUR THINGS THIS PAGE ASSERTED WERE WRONG, and each is corrected in place
rather than quietly amended:
* "10 dangling citations" was ONE. Eight were DIRECTORY references, which
resolve and are absent only from `git ls-tree`; one was a path ending a
sentence, with the full stop pulled into the match. I reported that 10 to
the human twice.
* `extract -r` is not how the .pe was made and nothing depends on it. The
plain extract is, and the tool Phase 3 keeps reproduces it byte-for-byte.
* The Phase 0 gate compared branches by SHA and called seven safe branches
unpushed -- a branch that is an ancestor of a pushed one is preserved.
* There is no DirectXShaderCompiler drift; the ' m' is nested submodule dirt.
The pattern is worth more than the corrections: every claim that was REASONED
turned out wrong, and every one that was RUN held. Phase 2's gate refuted its
own premise, Phase 3's proved two alarming row deltas were an improvement, and
Phase 4's check caught a deletion I had just made. The page now says to re-run
the gates rather than trust the results recorded in it.
Phase 5 (archive) and Phase 7 (drops) are open and marked NOT STARTED, with
the reason each needs the human: archiving is reversible and deleting a
repository is not, and the drops are on the human's disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CONSOLIDATION.md Phase 6. Both lived untracked in the project root -- on one
disk, backed up by nothing.
tools/ppc-manual/ 393 files, 3.7 MB. 455 instructions, 350 family pages,
598 mnemonics resolvable through index.json, plus the
generator that produced them.
tools/run-canary.sh the oracle launcher.
🔴 THE LAUNCHER WAS BROKEN IN TWO WAYS AND IS REWRITTEN, not copied:
* it pointed at `xenia-rs/sylpheed.iso`, a SYMLINK. Wine cannot resolve one
and says "path invalid", which reads as a corrupt image rather than a path
problem -- it has cost a session before. It now points at the real file and
warns if handed a symlink.
* it hardcoded one machine's absolute paths, and named `xenia-rs`, which this
consolidation retires. Now derived from the script's own location, with
SYLPH_CANARY_BIN / SYLPH_ISO overrides and a check that each exists.
The standing constraints are in its header where someone will read them: one
emulator at a time, Canary runs MUTED, and never judge a crash or a hang from
a Bash-launched run -- a SIGKILL that looked like the binary was the editor's
process supervisor.
⚠️ The manual's GENERATOR reads the xenia-rs source tree, which is going away.
Its decoder now lives here as crates/sylpheed-ppc, so the generator must be
repointed before it is run again. Recorded in the README rather than left for
someone to discover; the manual's content is checked in and regenerates from
nothing implicitly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CONSOLIDATION.md Phase 4. `tools/port/check-citations` has checked
`docs/port/*.md` since it was written; `docs/re/` never had one, and
`docs/re/captures/` is the largest thing in the repository -- the one place a
file can be added, never cited, and never noticed.
captures committed : 212
cited by a page or a tool : 212
cited but NOT committed : 0
🔴 THE PREMISE I STARTED FROM WAS WRONG FOUR TIMES, AND EACH CORRECTION IS IN
THE TOOL RATHER THAN JUST IN MY HEAD:
* "10 dangling citations" -- the real number was ONE. Eight were DIRECTORY
references, which resolve and are simply absent from `git ls-tree`; one was
a path at the end of a sentence with the full stop pulled into the match.
A checker that cries wolf nine times in ten teaches you to ignore it.
* The one real dangler, `live-submenu-unidentified.png`, was COMMITTED and
then deleted on 2026-08-30 while the page kept citing it and kept making
the claim it backs. Restored from 77e54e95 rather than dropping the link:
deleting evidence while keeping the conclusion is the thing this corpus
exists to prevent.
* Two "orphans" are opened BY FILENAME from `screen_match.py`, which builds
the directory separately. A path-only scan called them unreferenced and
deleting them would have broken the tool. The check now counts any
basename named anywhere.
* Pruning then EMPTIED two directories that pages cite as directories, and
the check went red on the very citations that made them evidence. It
caught its own damage. A file inside a cited directory is cited.
The selftest covers all of it, including the failure that actually happened:
an earlier version passed five green ticks while the scan silently returned
NOTHING, because `git grep -E` is POSIX ERE and cannot compile `(?:`. A
selftest that cannot see the failure that occurred is decoration.
46 orphaned captures dropped, 30.7 MB from the checkout. ⚠️ That reclaims no
repository space -- the blobs stay in history -- and it is not meant to. It
means every capture here is now evidence some page or tool actually uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shifts clippy objects to are the point. These are instruction ENCODERS:
every field is written at its ISA position, so `(((vb >> 5) & 0x3) << 0)`
sits in a column with << 11, << 16, << 21 and reads straight against the
manual's field table, and a trailing `| 0` says 'the low field is zero
here', which is information.
Reduced to what clippy wants, the encodings stop being checkable by eye
against the ISA -- and eye-checkability is all a golden GENERATOR has. There
is nothing else to verify it against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written for the other machine, which asked. Everything in it was read off the
running host rather than remembered.
Also adds docker/ci/ -- the CI image recipe and a capped runner -- because the
image existed on exactly one host and its Dockerfile was in a scratch directory
under /tmp, which was swept. That is the same shape as every other thing this
consolidation has turned up: something correct that exists in one place.
docker/ci/Dockerfile rust 1.98.1 + the apt list copied from ci.yml
docker/ci/Dockerfile.ffmpeg + ffmpeg, which sylpheed-export shells out to
docker/ci/run 6 CPUs / 7 GB / NO SWAP, named cargo volumes
The rule the runner exists to enforce: every heavy command goes in the capped
container. CARGO_BUILD_JOBS caps codegen units, not rustc's threads, not the
linker, not the test harness -- a bare host build is unbounded and has frozen
this box repeatedly.
The document also records the two agent defects the other machine will meet:
a brief change does not reach a RESUMED session, and nothing brought an agent
back to its own red PR (fixed in #24, which is itself subject to the first).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three tests failed on the extracted tree for a reason that was not a port
defect: disasm_goldens.rs resolves its fixtures through a relative path to
xenia-cpu/tests/golden/, and I had copied the test without the data.
The fixtures (base + extended mnemonics, VMX128 registers) and the generator
test now live in sylpheed-ppc, and the shim test in sylpheed-xexdb points at
them. serde/serde_json become dev-dependencies of sylpheed-ppc, which the
fixtures need and the library does not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written for the other machine, which asked. Everything in it was read off the
running host rather than remembered.
Also adds docker/ci/ -- the CI image recipe and a capped runner -- because the
image existed on exactly one host and its Dockerfile was in a scratch directory
under /tmp, which was swept. That is the same shape as every other thing this
consolidation has turned up: something correct that exists in one place.
docker/ci/Dockerfile rust 1.98.1 + the apt list copied from ci.yml
docker/ci/Dockerfile.ffmpeg + ffmpeg, which sylpheed-export shells out to
docker/ci/run 6 CPUs / 7 GB / NO SWAP, named cargo volumes
The rule the runner exists to enforce: every heavy command goes in the capped
container. CARGO_BUILD_JOBS caps codegen units, not rustc's threads, not the
linker, not the test harness -- a bare host build is unbounded and has frozen
this box repeatedly.
The document also records the two agent defects the other machine will meet:
a brief change does not reach a RESUMED session, and nothing brought an agent
back to its own red PR (fixed in #24, which is itself subject to the first).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rustfmt, then clippy -D warnings across the three new crates. Mechanical,
except three decisions that are stated rather than silently allowed:
* lzx.rs gets file-scoped needless_range_loop/explicit_counter_loop allows.
Index arithmetic IS the algorithm -- LZX is defined over symbol indices,
Huffman slots and window positions, and a decompressor that is merely
idiomatic is worth nothing if it is not bit-exact.
* sylpheed-xexdb gets crate-scoped allows for needless_range_loop (nine
sites index reg[r] where r is the PowerPC register number -- the index is
the meaning), too_many_arguments and type_complexity. This code arrived
whole from a retired repository; a refactor here would be an unreviewed
edit dressed as a lint fix.
* Everything else clippy asked for is FIXED, including all 14 doc-indent
sites, the let-else, and a Prepared type alias in the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>