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>
`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>
`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>
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>