Three earlier "the title never appears" claims came from instruments later found broken -- a stale pixel oracle, a 41 s sampling interval, a freezing stream. This one carries its own evidence. title_probe_xchecked.py restarts its capture stream every 30 s AND prints its reading beside an independent `import` grab every 60 s: 1851 frames in 560.2 s = 3.30 fps cross-checks 9, disagreements 1 max glyph 0 t= 62s stream 6.05 | import 0.07 disagree (a fade, logos mid-transition) t=123s stream 7.40 | import 7.49 agree t=183s stream 8.18 | import 8.29 agree t=243s stream 0.23 | import 0.10 agree t=311s stream 89.68 | import 89.51 agree t=371s stream 80.97 | import 81.58 agree t=426s stream 117.43 | import 117.72 agree t=487s stream 77.71 | import 76.25 agree t=546s stream 70.43 | import 70.55 agree Eight of nine agree within 2%, fps held at 3.30 with no collapse to 1.60, and the surface moved through dark and bright phases. So the frames were live: over 560 continuous seconds from launch, sampled 3.3 times a second, the interactive title's green (A) plate never appears while the game renders throughout. The final frame correlates 0.0145 / -0.0047 / 0.0102 with our title / main menu / EXTRAS renders -- attract-movie content, not a UI screen. Why remains unknown. live-title-press-a.png with its 753 glyph pixels proves the title was reachable from this container on 2026-08-28, and clearing the shader cache fixed the black surface but not this. The two emulator-side questions (gamma control, 8AX vs ptbase) are therefore blocked on a characterised failure rather than a suspicion. Neither blocks the five menu screens, so I am returning to static work; the probe is committed for whoever picks it up. METHOD: a probe that cross-checks itself turns "no result" into a result.
15 KiB
Primary objective — answer everything the menu port needs
Status: active, set 2026-08-28. This replaces "work the RE backlog" as the agent's primary objective. It does not change what the agent does — it is still reverse engineering — it changes what earns attention: an item is worth doing when the menu port is blocked on it.
Who builds what
You do not build the port. A separate agent will build it, from what you produce. Your deliverable is decoded, verified, written-down answers plus the reference data that proves them.
| container agent (you) | port agent | |
|---|---|---|
| decodes the disc | ✅ | ❌ — consumes your answers |
| measures the running game | ✅ | ❌ — no emulator |
writes docs/re/ and docs/port/HANDOFF.md |
✅ | reads them |
| Godot project, asset pipeline, transcoding | ❌ | ✅ |
If you find yourself designing an export schema or writing GDScript, you have crossed the line. Stop and go back to the question you were answering.
crates/sylpheed-viewer is also not yours to change for this objective. The
Explorer is the human's tool for exploring and verifying the RE work, it keeps its
static-data-only rule, and the port does not depend on it.
The target
Someone else has to build this, from your answers alone:
developer logo splash → intro video → title / PRESS Ⓐ → main menu → submenus
No gameplay, no 3D, no HUD, no missions. If an answer is not needed to put those five screens on a display and let a person move through them with a d-pad and Ⓐ, it is not in this objective.
What is already answered
Do not re-derive these. They are in docs/re/ and the
disc atlas:
- The screen archive.
GP_TITLE.pakholds the whole title-side tree — build 4 the title with animating wordmarks, build 5 the five-button main menu, builds 6/8/9 submenus, and the developer splash as thepalogobundle in the same pak. - Buttons are identifiable as data. Element kind
0x3002is a button,0x0decoration,0x10a primitive; buttons sort top-to-bottom by resting Y; each pairs with anf-suffixed highlighted variant. - The screen vocabulary. The GamePart id table, 29 entries at
.rdata 0x820A1630, confirmed by the executable's own factory-registration strings. - The resting pose rule — the hold, not the longest dwell — and that a keyframe is the start of a ramp.
- Screen composition, pixel-accurate for the tutorial pause menu and the title
main menu, via
sylpheed-cli screen render. - The logo splash is not a video.
logo1–logo4are manifest-bound with no.wmvon the disc; the splash is the RATC screen, which already renders.
The open questions — these are the objective
Ordered by what blocks the port earliest. Each is done when its gate exists:
a written docs/re/ result with the evidence, and reference data committed
alongside it.
| Question | Gate | |
|---|---|---|
| Q1 | What is a keyframe time? Values run 16…269. 60 Hz frames would make the title intro ~4.5 s — plausible and untested. Also: is the ramp linear, or eased? | A measured answer against the running game, not an inference. Everything animated downstream depends on this number |
| Q2 | Which build is which screen state? Confirm build↔state for splash, title/PRESS Ⓐ, main menu and each submenu | A table, each row confirmed against a capture of the real screen |
| Q3 | Paint order for these six screens. Solved at runtime, unsolved from the file — the declaration table is provably not it | Either a rule derived from the bundle, or six measured orders and a clear statement that no file-side rule was found |
| Q4 | What does each button do? Labels are baked into the sprites; no decoded field says which GamePart a button opens | Button → GamePart id, from code or from driving the game. Say which |
| Q5 | Navigation semantics. Initial focus, wrap-around at the ends, whether left/right does anything, what B does on each screen | Observed behaviour, per screen |
| Q6 | The boot sequence, and what drives it. Order is observable; the data or code that sequences it is not decoded. Include the attract loop and what returns to the title | The sequence, plus whatever the game reads to decide it |
| Q7 | Transitions. What happens visually between screens — the pteff00.prm quads, a fade, a cut — and its timing |
Described and timed against a capture |
| Q8 | Menu audio. Which BGM per screen; which cue on move / confirm / back / error. The cue table is complete; the event binding is not | Cue names bound to events, with how you established each |
| Q9 | Video binding. Which movie is the boot intro vs the new-game intro; whether playback is skippable and what ends it | Named movies plus the playback rules |
| Q10 | What are a music bank's sub-waves? BGM_001.slb is three sub-waves — 10 KB, 4.47 MB, 4.67 MB — and we currently concatenate them blindly into one 347 s track. Two near-equal halves could be intro + loop, or two variations, or two halves of one piece. A menu that loops its music needs to know which |
The role of each sub-wave, established for at least the menu BGM. "Concatenate" is a decision, not a default — right now it is a default nobody chose |
| S1 | ✅ go/no-go written |
🔴 Emulator-side questions are blocked — the title is not reachable here
Status 2026-08-29, instrument-verified. Two open items need a running menu:
the gamma control behind tone curve,
and separating 8AX from ptbase in
8AX. Both are blocked.
✅ The negative is now solid. A probe that restarts its capture stream every
30 s and cross-checks itself against an independent grabber every 60 s
(9 checks, 8 agreeing to within 2 %) ran 560 continuous seconds from launch at
3.30 fps: the interactive title's green Ⓐ plate never appeared, while the game
rendered throughout. The final frame correlates 0.0145 / −0.0047 / 0.0102 with
our title / main-menu / EXTRAS renders — attract-movie content.
❔ Why is unknown. live-title-press-a.png (753 glyph pixels) proves it was
reachable from this container on 2026-08-28. Clearing the shader cache fixed a
black surface but not this.
⚠️ Neither blocked item blocks the five menu screens. See capture-harness-status for the full trail, including three earlier "the title never appears" claims that were withdrawn because the instrument was broken each time.
🟡 Needs one more run — a Japanese-locale capture
Recorded rather than worked around, per "do not improvise around a blocker".
Two questions have converged on needing one capture we cannot take: a running
capture of GP_TITLE build 7, the Japanese title screen.
- Q1's keyframe-time association. The shifted reading is favoured 26× by a
calibration-free measurement, and the only render it changes on the whole disc
is build 7 (
ui-keyframe-time-unit.md). - What
rest()should return for a plateau-less element. The one element that discriminates,ptlogo_eff3.t32, is also in build 7 (structures/ui-resting-pose.md).
🔴 The "cannot" was wrong, and is withdrawn (2026-08-29). It is true that
user_language is only ever DECLARE_int32 here, with no DEFINE and no entry
in xenia-canary.config.toml, so there is no flag to pass — and passing an
unknown one is specifically dangerous, because run-canary's own header records
that xenia calls ShowSimpleMessageBox from ParseLaunchArguments before
logging starts. But the cvar is not the only route, and I stopped at the first
one I checked.
The language is persisted, and canary's own file is writable.
kernel_state.cc builds XConfig over <storage_root>/xconfig.settings, which
exists here at /sylph-home/re/.local/share/Xenia/xconfig.settings (6 680 B).
user.language is a big-endian u32 at file offset 0x912, located by three
independent landmarks rather than guessed:
| landmark | expected | found |
|---|---|---|
music_volume (User+449) |
0.7f |
BE float at 2727 → User base 0x8e6 |
language (User+44) |
1 = kEnglish, the hard-coded default |
1 at 0x912 |
country (User+64) |
United States | 103 at 0x926 |
XLanguage::kJapanese = 2 (xbox.h:307). Writing 2 there and restoring
afterwards is using canary's own persistence, not patching its code.
🔴 DIAGNOSED 2026-08-29 — the harness, not the game. screenshot costs
10.8 s while xenia is running and 0.117 s once it is killed (92×, at a
1-minute load average of 1.80, so it is contention with the emulator). A
two-grab polling loop therefore samples every ~41 s, against a title screen
this corpus documents as lasting a few seconds. Four runs — two locales, two
launch paths, two gamma settings — were all blinking slower than the event.
🔴 …and that diagnosis is itself refuted (same day). The faster probe was
built — 3.98 s → 0.29 s per sample, 13.7×, control-verified at 753/327 — and
at 3.99 fps for 420 continuous seconds (1 674 samples) the interactive title
still never appeared. Sampling rate was a real defect and not the cause.
✅ So "the interactive title does not appear mid-run without a pad press" is
reinstated, now as a dense measurement. ⚠️ Its reach is a mid-run window; it
says nothing about the boot title.
🟡 Leading hypothesis, unconfirmed: the plate appears only in the boot title
window and the attract loop's title has none — which is precisely what
title_states_capture.sh was written to test. The experiment is to start the
fast probe from t=0, not attach to a run already in progress.
See capture-harness-status.
🟡 Still not settled — two runs, and the reason moved again.
Run 1 (2026-08-28) reported "title not seen" in 787 s. That was a broken
tool, not the game. wait_title.sh still carried the single-pixel oracle that
is_title.py was written to replace — a 1280×720 coordinate sampled against the
1279×675 game surface, so it always reads the copyright line. Fixed; it now
delegates to is_title.py.
Run 2 (2026-08-29), with the working oracle and the profile flag the English
captures use, still did not reach the interactive title. Not one frame in the
run showed a single green-Ⓐ glyph pixel, and content correlation against either
build-7 render never exceeded 0.22. The game sat in the attract movie
throughout, polling XamInputGetKeystrokeEx (601 calls) — i.e. alive and waiting
for input, not hung.
✅ The control was run (2026-08-29), and it removes the locale from the
picture. Same flags, same oracle, English locale: 75 samples over 734 s, every
one glyph = 0. The English boot does not present the interactive title either.
Canary was alive throughout and polling XamInputGetKeystrokeEx (1 801 calls);
the frame at 734 s has content but correlates only 0.15 with build 4's render
and 0.05 with the main menu, i.e. it is an attract-movie frame, not title art.
So the Japanese run was not failing because of the locale — neither locale
reaches the interactive title in ~12 minutes of no pad input. 🔴 Tried the proven path too, and I am stopping this line (2026-08-29).
Run 3 launched exactly the way boot_menu.sh does — DISPLAY=:98, --apu=sdl,
/dev/shm/xenia_* cleared, the existing profile signed in — and drove
skip_intro.sh, the detector that is documented to work. It classified every
one of 20 samples over 604 s as "movie" and waited them all out, then timed
out. A direct check at 604 s confirms it was right: zero green-glyph pixels,
screen_id = other, warm mean (69,53,40), correlation 0.09 with build 7. The
game really was playing attract movies for ten minutes.
So: three runs, two locales, two launch paths, ~35 minutes of emulator time, no
interactive title. Either the attract loop is far longer than the 600–780 s
windows tried, or something has regressed since live-title-press-a.png was
captured (that one came from a pad-driven boot — commit 88b3ce9, "booting to
the main menu and walking it").
⚠️ Not worth more loop iterations. This capture does not block any of the five menu screens, and it has now cost five. It is written down so a later session with a reason to spend an hour on the boot path can pick it up; the locale mechanism, the fixed oracle and the launch recipe are all in place, so what is left is patience with the attract loop, not tooling.
The locale is restored to English; set_console_language.py ja flips it back in
one command.
⚠️ Neither question blocks the five menu screens. Q1's interpolation law is
settled and only multi-keyframe absolute timing is open; rest() differs from
its alternative on one element across all five screens, and the current
answer there is the defensible one.
Known unknowns — say so, do not fill them in
Some of these may turn out to be undecodable. That is a valid, useful answer, and it is better than a guess, because the port agent will otherwise have to author the mapping by hand and needs to know it is authoring rather than transcribing.
For each question, the answer is one of:
- decoded — here is the field, here is the disc-wide check;
- measured — not on the disc in any form we found, but here is what the running game does, and here is the capture;
- undecodable, with reach — we looked here, here and here, and this is why it is not there.
Never a fourth thing. In particular: if Q4 ends as "read the labels off the sprite images by eye", say exactly that — it is then an authored mapping on the port side, not a disc fact, and mislabelling it would put a guess into the port wearing the badge of a measurement.
The Ready Room probe (S1) — one iteration, then stop
GP_READY_ROOM.pak is the largest UI archive on the disc, 1 106 entries, and only
6 of its names resolve. It is also ISL-scripted. That is either a week or a
quarter, and one cheap test tells you which.
Our screen catalog enumerates bundles by content, not by name — is_build /
is_composable read the bytes — so unrecoverable paths do not necessarily mean
unrenderable screens.
sylpheed-cli screen list "$SYLPHEED_DISC/dat/GP_READY_ROOM.pak"
sylpheed-cli screen render --build <n> "$SYLPHEED_DISC/dat/GP_READY_ROOM.pak" /tmp/rr.png
Report how many builds it finds, whether any composite looks like a Ready Room, and whether the room is 2D at all or 3D with a UI overlay — if it is 3D the answer is no-go by definition, not "try harder".
Then stop and write the go/no-go. Do not start Ready Room work on your own authority.
Handing it over
HANDOFF.md is the single page the port agent reads. Keep it
current as you answer questions: it is a summary with links into docs/re/, not a
second copy of the findings. An answer that is not reachable from HANDOFF.md has
not been delivered.
Out of scope
3D, gameplay, HUD, missions, save/load, localisation beyond English, the Godot
project itself, any asset pipeline, and any archive outside GP_TITLE,
tables.pak, sound.pak and dat/movie/ — except for the S1 probe.