Files
Sylpheed/docs/port/MISSION.md
sylph-decoder 4dd6a3f325 re: a bank's wave 1 is not a filtered copy of wave 0 -- and my own discriminator cannot finish the job
Coherence on BGM_103, the menu's bank, with controls run first: a real linear
filter of wave 0 reads 0.93-0.94 in every band, a different bank reads 0.001, and
wave 0 misaligned by 1 s reads 0.004-0.057. The measurement reads 0.027 at 1-4 kHz,
so the 'wave 1 is wave 0 filtered' model is refuted.

The frequency structure is inverted relative to any mic-pair or reverb model:
coherence rises with frequency (0.169 -> 0.827) while energy falls (71 % -> 0.2 %),
and a rear pair decorrelates fastest at HF. In the midrange the two waves are 13x
further apart than the two channels of one wave.

But the L-R control is what limits the tool and it is recorded as such: within one
wave, genuinely one performance in two channels, coherence is only 0.221-0.497. So
'same performance' does not imply high coherence here, my positive control was the
wrong model of the rear-pair reading, and the 🟡 is NOT settled. The tool tests for
linear filtering and neither surviving reading requires it.

Also corrects MISSION's Q10 row, which still carried the refuted three-sub-wave
premise and had directed work at a dead question for days. Its gate is in fact met.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 20:17:23 +00:00

406 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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?**~~**ANSWERED — see below.** Every factual premise in the original row is refuted: a bank is **two** waves, not three (the 10 KB was the bank *header*, emitted by our own reader), and the three candidate roles it listed — intro + loop, two variations, two halves — are all dead. | ✅ **Gate met.** Role established on the menu's own bank: [`bgm-two-stems.md`](../re/structures/bgm-two-stems.md). 🟡 One sub-question survives — *which kind* of second stem — and it is 🟡 by measurement, not by neglect |
| **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 NOT blocked — corrected 2026-08-30
⚠️ **This heading read "🔴 Emulator-side questions are blocked — the title is not
reachable here".** That is false and has been for some time: **twelve** emulator
runs on 2026-08-30 reached the settled title, gated on the plate pulse, and drove
it into the menu, `EXTRAS` and out of the archive. `HANDOFF.md` recorded the
banner as withdrawn; this document did not, and it is the one the brief says to
read **every iteration**.
🔴 **My first correction of this section was itself wrong, on all three clauses,
and is replaced (2026-08-30).** It read: *"The two items this section named are
UNBLOCKED, not answered … Both need a running menu, both now have one, and neither
has been attempted."* I wrote that without reading either page. Reading them:
* **`8AX` vs `ptbase` was RESOLVED on 2026-08-29.** Its page says so in its status
line — both its questions closed, kept for the evidence.
[8AX](../re/structures/ui-8ax-fullres-background.md)
* **The gamma control was attempted and half-answered**, and its page records that
the run *"needed the emulator only to **boot**, not to reach a menu … parked
behind the title-screen blocker for no reason."*
[tone curve](../re/structures/ui-render-tone-curve.md)
* **So neither ever needed a running menu**, and this section's premise was wrong
independently of whether the menu was reachable.
⚠️ **A correction is a new claim.** Mine replaced a stale status with an unchecked
one, in the same edit that criticised the document for carrying unchecked status —
which is the failure I had just finished cataloguing elsewhere.
The original section is kept below for the record, demoted so it cannot be read as
current.
**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.
## ✅ The Japanese-locale capture was taken — TWICE. Corrected 2026-08-30.
⚠️ **This heading read "🟡 Needs one more run — a Japanese-locale capture", and the
text below calls it "one capture we cannot take".** Both are false.
[`live-title-jp-at-rest.png`](../re/captures/title-builds/live-title-jp-at-rest.png)
and [`-run2`](../re/captures/title-builds/live-title-jp-at-rest-run2.png) are
committed, from two independent sessions, via
[`jp_title_session.sh`](../../tools/re-capture/jp_title_session.sh) which sets the
console language and always restores it.
📌 **And both questions it was blocking are closed** — Q1's association by the
[record-layout fix](../re/ui-keyframe-record-layout.md), and `rest()` for a
plateau-less element by a 1 036/1 036 discriminator, then **confirmed against this
very capture**: the corrected pose scores RMSE 41.69 where the stale one scores
58.41 ([`ui-resting-pose.md`](../re/structures/ui-resting-pose.md)).
⚠️ I noticed this section was stale several iterations ago, said so in a message,
and did not fix it. Kept below, demoted.
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 600780 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.
## ✅ Rotation — the decision was TAKEN and implemented. Corrected 2026-08-30.
⚠️ **This heading read "🔵 Needs a human decision — rotation (raised 2026-08-29)".**
It was answered the same day it was raised: `HANDOFF.md` records *"OPTION A IS
DONE. The reference renderer rotates"*, and `ui_layout.rs` carries the rotated blit
with a control test (`rotation_control_known_angles`) pinning it against angles
whose answer is arithmetic — 0° and 360° byte-identical to the unrotated path.
**And the field itself is now confirmed from the ORACLE, not just decoded.** The
port's `rotation_deg` of **+30** on `pteff03` and **45** on `pteff03a` predict a
rotated quad's AABB height at 1135.3 and 1301.1; the game's draw stream measures
**1134** and **1303** — both under 0.2 %
([`data/title-sweep-drawn-at-rest.txt`](../re/data/title-sweep-drawn-at-rest.txt)).
The original section is kept below, demoted.
The port agent asks whether it should **render** `rotation_deg` (decoded at
keyframe `+12`) when `sylpheed-cli screen render` deliberately does not. Its own
framing is the reason this is not mine to settle: if the port rotates and the
reference renderer does not, then `verify-screen` reports a large title diff that
means *"the port is right"* — a silently inverted signal.
The RE half is answered and is in HANDOFF: rotation is about the **declared
pivot** (measured against a GPU capture, not assumed), and it changes nothing on
the five screens **at rest**.
What needs a decision is which way the divergence gets closed:
* teach `ui_layout::blit` a rotating path, so the two renderers stay comparable
and the diff keeps meaning "someone is wrong" — costs work in the reference
renderer, which is otherwise not on the port's critical path; or
* let the port render rotation and mark the title as a known-divergent screen in
`verify-screen`, accepting a check that no longer guards the title.
Recorded rather than chosen, per "do not improvise around a blocker".
## ✅ Q10 is answered — corrected 2026-08-30
The Q10 row above was written on a premise that has since been **refuted in every
part**, and it survived as a live question for days after the refutation landed.
Recording the correction here rather than silently editing the row:
**"`BGM_001.slb` is three sub-waves (10 KB, 4.47 MB, 4.67 MB)"* — the 10 KB is
the **bank header**. Our reader emitted it, from a modulus valid only for a
header shorter than one XMA packet. A bank is **two** waves, **28/28** disc-wide.
**"we currently concatenate them blindly"* — and concatenating is **wrong**,
now measured: the two waves are **sample-synchronous** and the running game
decodes **both at once** (the XMA probe at the main menu saw two stereo streams
whose byte sizes are `BGM_103`'s two declared waves, exactly).
**"could be intro + loop, or two variations, or two halves of one piece"* — all
three predict unequal durations; **32 banks give equal ones**.
***The gate — "the role of each sub-wave, established for at least the menu
BGM" — is met**, and on the menu's own bank: `BGM_103`, named from the
executable, confirmed against the disc and against the running game.
🟡 **What is still open is narrower than the row**: *which kind* of second stem —
the rear pair of a 4-channel mix, or a second intensity layer. Both predict
simultaneity, so runtime observation cannot separate them, and a coherence
discriminator run 2026-08-30 **refuted the "filtered copy" model but could not
separate the two** — its own control showed that in this material even L vs R of
one performance reads only 0.220.50, so the test's premise does not hold.
[`../re/data/bgm-stem-coherence.txt`](../re/data/bgm-stem-coherence.txt)
⚠️ **This distinction does not block the port.** Both readings give the same
instruction: play both waves, aligned at sample 0, together. It changes only how
they would be *mixed* if the port ever does surround.
## 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.
## Publishing a decoder state the port pins
The port's exporter depends on `sylpheed-formats` **by revision**, so a commit of
yours becomes part of its build. That creates an obligation that is easy to miss:
⚠️ **A commit reachable only from an `auto/*` branch is not safe to pin.** If the
branch is later deleted, or — worse — **squash-merged**, the object is orphaned.
Squash creates *new* commits, so `main` appears to contain the work while the
pinned sha becomes unreachable, and the port stops building for anyone doing a
fresh checkout. Silently, at their build, long after the moment of breakage.
**So when you land something the port needs, tag it:**
```bash
git tag -a formats-pin-$(date +%F) -m "what the port gets from this state"
push-work # --follow-tags publishes annotated tags with the branch
```
A tag is a permanent ref. It survives branch deletion and squash-merge, it is
self-documenting in the port's `Cargo.toml`, and it fails loudly at *fetch* if it
ever goes missing rather than silently at build.
Then tell the port over the message channel that a new pin exists. It bumps
deliberately, as its own commit — that is how it stays current without floating,
which would only give it staleness it cannot see.
`formats-pin-2026-08-29` at `7eeae30` is the first, created because the port had
already pinned a commit that lived on one topic branch and nowhere else.
## 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.