The port put two of my pages against each other: settle->plate 2.135 s and one
focus-ring revolution 2.177 s, both a declared 120 units during a static hold,
2% apart against a 6 ms run-to-run agreement. Fair challenge.
The resolution is that the question assumes a stable wall clock. Same interval,
same container, same day: 2.138, 2.132, and 2.549 s -- a 19% swing, caused by
adding --log_ui_draws=true. The 2% is a fifth of that. The two pages were never
in conflict about the game; they are three readings of one declared quantity
through a clock that moves. What settles the quantity is the disc.
Wall clock cannot separate the hypotheses, so I tried to measure frames instead.
Both instruments are recorded as failures rather than published as numbers:
* Canary's own [UI-CAP] counter -- the one that produced the corpus's 28.5 fps
-- costs a third of the frame rate. 300 frames in 16.567 s = 18.11 fps on a
screen that gives ~28 without it. That reclassifies 28.5 as a load-dependent
lower bound; it does not overturn it.
* A distinct-frame counter over the spinning ring FAILED its decisive control:
15.88 fps against the game's own 17.59 in the same window, 10% low, so the
ring does not change on every presented frame. Its static control also read
2.63 instead of ~0. Dead, not tuneable, per METHOD.md.
The rule that follows, and it applies to everything I hand the port: a measured
interval landing near a round number of declared units almost certainly IS that
number of units. Ship the units.
Also recovered here, because the same question needed it: the static PPC route.
Four tools open /work/xenia-rs/sylpheed.db and nothing in this repository builds
it -- no disassembler, no PPC decoder, and default.xex is encrypted (zero
plaintext "GamePart"). Xenia decompresses the image at load, so dump_image.py
reads it out of guest memory and validates it against the corpus's own landmarks:
the 29-entry GamePart id table at 0x820A1630 and the Xbox 360 D3D runtime
strings. String search and table dumps work again; instruction-level work does
not, and the present interval I wanted is an immediate, not a string.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
105 lines
4.5 KiB
Markdown
105 lines
4.5 KiB
Markdown
# 🟡 The static PPC route was lost with the container migration — the image is back, the disassembly is not
|
|
|
|
**Status:** ✅ the **image** is recovered and validated; 🔴 the **disassembly
|
|
database** it is analysed with does not exist in this repository at all, and
|
|
never did. 2026-08-29.
|
|
|
|
## What broke
|
|
|
|
Four tools in `tools/re-capture/` open a DuckDB database at
|
|
`/work/xenia-rs/sylpheed.db` — `name_block_bases.py`, `archive_naming.py`,
|
|
`isl_cmdtab.py` and the census work that produced most of the `sub_82xxxxxx`
|
|
findings in [`INDEX.md`](INDEX.md). In this container:
|
|
|
|
```
|
|
$ ls /work/xenia-rs
|
|
ls: cannot access '/work/xenia-rs': No such file or directory
|
|
```
|
|
|
|
🔴 **And nothing in the repository builds it.** A grep for `duckdb` finds only
|
|
*consumers*; there is no disassembler, no PPC decoder vendored, and `capstone` is
|
|
not installed (and `pip install` is refused here by PEP 668). So the static route
|
|
is four read-only clients of a producer that is not in the tree.
|
|
|
|
⚠️ **This is not a small gap.** Every finding that cites a function address —
|
|
the GamePart registry, the challenge gate, the ISL command table, `PlayerParams`,
|
|
the boot sequencer — is currently **unre-checkable in this container**. They are
|
|
not wrong; they are unverifiable, which is a different and quieter problem.
|
|
|
|
## `default.xex` is not a substitute
|
|
|
|
The disc's executable is encrypted and LZX-compressed:
|
|
|
|
```
|
|
$ xxd -l 16 /disc/default.xex
|
|
00000000: 5845 5832 0000 0001 0000 3000 ... XEX2......0.
|
|
$ strings -a /disc/default.xex | grep -c GamePart
|
|
0
|
|
```
|
|
|
|
The header is intact — `XEX2`, media id `535107D4`, original PE name
|
|
`default.pe` — and everything after it is noise. No decrypt/decompress exists in
|
|
the tree either.
|
|
|
|
## ✅ What is recovered, and how it is validated
|
|
|
|
Xenia decrypts, decompresses and relocates the image at load, so a **running
|
|
guest holds the flat VA image** the corpus calls the `.pe`
|
|
(`VA = 0x82000000 + offset`). `tools/re-capture/dump_image.py` reads it straight
|
|
out of `/dev/shm/xenia_memory_*` — no debugger, no emulator patch, no pause.
|
|
|
|
```
|
|
$ tools/re-capture/dump_image.py /sylph-home/re/sylpheed-image.pe
|
|
wrote ... 4194304 bytes VA 0x82000000..0x82400000
|
|
validated: GamePart id table + D3D runtime strings; 1013/1024 non-empty 4K pages
|
|
```
|
|
|
|
**The validation is the corpus's own landmarks, not the tool's.** A mis-based or
|
|
partial dump fails both:
|
|
|
|
* `0x820A1630` holds the **GamePart id table** — 29 `.rdata` pointers resolving
|
|
to `GP_TITLE` (0) … `GP_TEST` (28), with `GP_CHALLENGE` at **26**, exactly as
|
|
[`challenge-mission-gate.md`](challenge-mission-gate.md) records;
|
|
* the image carries the **Xbox 360 D3D runtime's own error strings**
|
|
(`ERR[D3D]: Unanticipated CPU_INTERRUPT`, `D3D9D.LIB`), which only the real
|
|
loaded executable has.
|
|
|
|
So string search, table dumps and pointer chasing work again today. **What does
|
|
not** is anything needing decoded instructions: no `mnemonic`/`operands`, so no
|
|
xref search, no base solving, no call graphs.
|
|
|
|
⚠️ It also depends on a **booted emulator**, which is a bad dependency for the
|
|
foundation of the static corpus. Dump once and keep the file.
|
|
|
|
## 🔵 For the human — what is actually needed
|
|
|
|
Not the image; that is solved. What is missing is the **producer of the
|
|
database**, and its schema, which the four consumers pin exactly:
|
|
|
|
| table | columns used |
|
|
|---|---|
|
|
| `functions` | `address`, `end_address`, `name` |
|
|
| `instructions` | `address`, `mnemonic`, `operands` |
|
|
| `strings` | `address`, `content` |
|
|
|
|
An XEX unpacker would also be worth having on its own, so the static route stops
|
|
needing a running emulator to bootstrap.
|
|
|
|
## What it blocks right now
|
|
|
|
The one open question this iteration wanted it for: **the game's present
|
|
interval**, i.e. the step from *one submitted frame* to *1/30 s of real time*.
|
|
|
|
"2 units per submitted frame" is already grounded and emulator-independent — it
|
|
was read off a frame-indexed draw capture
|
|
([`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md)). The remaining link is
|
|
`D3DPRESENT_PARAMETERS.PresentationInterval` at device creation: `_ONE` means the
|
|
game runs at the console's 60 Hz vblank, `_TWO` at 30. That is a constant loaded
|
|
into a register, so **finding it needs disassembly, not string search** — the
|
|
image contains the D3D runtime but the interval is an immediate, not a string.
|
|
|
|
Settling it would move Q1's seconds-per-unit from **measured** to **decoded**,
|
|
and it is the only part of the UI clock still resting on a wall clock this
|
|
container has now been shown to move by 19 %
|
|
([`present-rate-instrument-failed.md`](present-rate-instrument-failed.md)).
|