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.
264 lines
15 KiB
Markdown
264 lines
15 KiB
Markdown
# 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](../re/disc-atlas.html):
|
||
|
||
* **The screen archive.** `GP_TITLE.pak` holds 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 the `palogo` bundle in the same pak.
|
||
* **Buttons are identifiable as data.** Element kind `0x3002` is a button, `0x0`
|
||
decoration, `0x10` a primitive; buttons sort top-to-bottom by resting Y; each
|
||
pairs with an `f`-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`–`logo4` are manifest-bound with no
|
||
`.wmv` on 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** | ~~**Ready Room probe.**~~ **DONE 2026-08-28 — [no-go](../re/ready-room-probe.md).** It is 2D and enumerates fine, but the pak is briefing/tactical-map content, not the Ready Room menu | ✅ 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](../re/structures/ui-render-tone-curve.md),
|
||
and separating `8AX` from `ptbase` in
|
||
[8AX](../re/structures/ui-8ax-fullres-background.md). 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](../re/capture-harness-status.md) 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`](../re/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`](../re/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](../re/capture-harness-status.md).
|
||
|
||
**🟡 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*.
|
||
|
||
```bash
|
||
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`](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.
|