# Handoff — what the menu port needs, and where it stands The single page the **port agent** reads. Everything here is produced by the container agent's reverse engineering; nothing here is a design decision about the port itself. Keep it current. It is a summary with links into `docs/re/`, not a second copy of the findings — but an answer that is not reachable from this page has not been delivered. ## How to read an answer Every row below is one of exactly three things, and the distinction is the point: | | meaning | what the port should do | |---|---|---| | **decoded** | a field on the disc, with a disc-wide check | read it from the data | | **measured** | not on the disc in any form we found, but the running game does *this* | hardcode it, and cite this page | | **undecodable** | we looked in these places, it is not there, here is the reach of the negative | author it by hand, knowingly | There is no fourth kind. If a row says *measured* or *undecodable*, the port is **authoring** that value, not transcribing it — and it should be kept somewhere a human can see it is a human decision, so that when it is later decoded the authored version can be deleted. ## Status | | Question | State | Answer / link | |---|---|---|---| | Q1 | keyframe time unit + ramp shape | ✅ answered | ramp is **linear**; the clock advances **2 units per rendered frame**; working conversion **1 unit = 1/60 s** — [`ui-keyframe-time-unit.md`](../re/ui-keyframe-time-unit.md) | | Q2 | which build is which screen state | ✅ answered | `GP_TITLE` is **8 screens shipped twice, EN/JP**: 4/7 title art, 2/3 the `PRESS Ⓐ` plate, 5/8 main menu, 6/9 `EXTRAS`, 0/1 and 10/11 two unidentified `DELTASABER` plates — [`ui-title-build-map.md`](../re/ui-title-build-map.md) | | Q3 | paint order for the six screens | ✅ answered | **decoded**: a `u16` layer key at `+0x0A` of each `T8aD` sprite header, stable-sorted with declaration index; unkeyed elements get an implied key. Confirmed on 5 measured orders + `EXTRAS` vs a capture. One residual: the **tie-break** is unknown and bites on one element of the title — [`structures/ui-paint-order-key.md`](../re/structures/ui-paint-order-key.md) | | Q4 | button → GamePart | 🟡 mostly answered | **measured** which screen each button opens (4 of 5; `NEW GAME` untested — it hangs). The **GamePart id is a name match** onto the decoded id table, not a measurement — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md) | | Q5 | navigation semantics | ✅ answered | **measured**: initial focus varies boot to boot (2× `TUTORIAL`, 1× `NEW GAME`); ⬆⬇ one step, **wraps both ends**; ⬅➡ do nothing; Ⓑ returns to the parent **with focus restored**; Ⓑ on the main menu → title; Ⓑ on the title → nothing — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md) | | Q6 | boot sequence + what drives it | 🟡 partial | order observed, and the **attract cycle is timed**: ~8–10 s idle on the title → fade to black → ~85 s of video → title again, plate and all. The driver is still not decoded | | Q7 | transitions | ✅ answered | a **fade through black**, drawn by the screen's own last-painting `.prm` quad. Fade-in ramp is **decoded** from its keyframes; the ~0.4 s fade-out is **measured** (not in the file) — [`screen-transitions.md`](../re/screen-transitions.md) | | Q8 | menu audio bindings | ❔ open | cue table complete, event binding is not | | Q9 | video binding + playback rules | 🟡 partial | `ADV.wmv` is the boot intro; new-game intro unidentified | | Q10 | music-bank sub-wave roles (intro+loop?) | ❔ open | we concatenate blindly today | | S1 | Ready Room go/no-go | ❔ open | probe not run | ## Already settled — the port can rely on these today * **`GP_TITLE.pak` is eight screens, each shipped twice — English and Japanese.** Build 4 is the English title art and 7 its Japanese twin; **2/3 are the `PRESS Ⓐ BUTTON` plate, a build of their own** composited over the title and faded in a beat later; 5/8 are the five-button main menu; 6/9 are the `EXTRAS` submenu — the **only** submenu inside this archive. Builds 0/1 and 10/11 are a `DELTASABER / SYLPHEED A.I.` plate that was **never seen running**, in the boot path, any title-side screen, or the attract loop. ✅ measured against live captures for the four English screens the boot path shows; [`ui-title-build-map.md`](../re/ui-title-build-map.md). **Withdrawn:** the earlier "builds 6/8/9 are submenus" — 8 is the Japanese main menu. The other four main-menu buttons leave the archive (`GP_SAVE_LOAD`, `GP_TUTORIAL`, `GP_OPTIONS`, `GP_MISSION_SELECT` are the likely destinations by name — an inference, not a measurement). * **Buttons are identifiable as data.** Element kind `0x3002` = button, `0x0` = decoration, `0x10` = primitive. ✅ decoded. * **Menu order is geometric.** Buttons sorted top-to-bottom by resting Y. This is ✅ correct for a vertical menu and is **not** a decoded neighbour graph — the disc's real navigation structure is unknown, and `opt ` is *not* a focus link (measured and refuted, see [`ui-focus-and-effect-elements.md`](../re/structures/ui-focus-and-effect-elements.md)). * **Highlighted states pair by name** — `ptbtn01.rat` ↔ `ptbtn01f.rat`. 🟡 a naming convention that holds for all 54 real pairs, not a decoded field. * **The resting pose is the hold**, not the first, last or longest-dwell keyframe; a keyframe is the **start of a ramp**. [`ui-resting-pose.md`](../re/structures/ui-resting-pose.md). ✅ * **That ramp is linear, and it runs at 2 keyframe time units per rendered frame.** Measured frame-by-frame off the running game's own draw stream: a declared 15-unit fade lands on `round(255·k/15)` for all seven of its samples, with `k` stepping 2, 4, 6, 8, 10, 12, 14 on seven consecutive submitted frames. **measured**, not decoded — the disc says `t=30`, it does not say what a `t` is. The seconds conversion (`1 unit = 1/60 s`, so a 30 fps screen) rests on a measured 27.6 present-frames/second and is the one part still worth re-testing; [`ui-keyframe-time-unit.md`](../re/ui-keyframe-time-unit.md) names the test. If it turns out the game presents at 60 Hz, every duration halves — nothing else on this page changes. * **The paint order is derivable from the file.** Each `T8aD` sprite header carries a **`u16` layer key at `+0x0A`** (the upper half of the 32-bit word at `+0x08` is zero in all 21 184 sprites on the disc). Paint order is that key, **stable-sorted** so equal keys keep declaration order; elements with no sprite (`.prm` primitives, `.tbm`) have no key and take an implied one — the backdrop and dim quads sort early, the screen-transition fade (`pteff00.prm`, `pfeff00.prm`) sorts **last**. ✅ decoded. Checked against five paint orders read off the running game, one of them (`GP_SAVE_LOAD`'s slot-list header, 6 instances) **exact and independent of the screens the rule was fitted to**, and against a fresh `EXTRAS` capture. It reorders 341 of the disc's 965 builds, so it is not a no-op dressed as a rule. 🟡 **The one residual: ties.** Where two elements share a key the game sometimes paints them in an order nothing predicts — eight candidates refuted, including declaration order, RATC child order, keyframe times, resting X/Y and `kind`. Measured cost: on the three screens with ground truth it changes the blend of **one element on one screen** (a title glow). Take the stable sort and accept that. * **Menu movement, measured off the running game.** ⬆⬇ move one item per press and **wrap at both ends** (5-item main menu and 3-item `EXTRAS` both). ⬅➡ do nothing. Ⓑ goes up one level **and restores focus to the item you came from**; Ⓑ on the main menu returns to the title; Ⓑ on the title does nothing. **Initial focus is not stable**: three boots of the same script gave `TUTORIAL`, `TUTORIAL`, `NEW GAME`. Do not hardcode it; pick one and say you picked it. All **measured**, none of it on the disc. * **Each button's destination is measured; its GamePart id is not.** `LOAD GAME` → the save-slot list, `TUTORIAL` → the lesson list, `OPTIONS` → the settings menu, `EXTRAS` → `GP_TITLE` build 6, `EXTRAS ▸ MISSION SELECT` → the stage list. ❔ `NEW GAME` is untested — Ⓐ on it hangs the emulator. The GamePart ids (`3`, `25`, `8`, `5`, `7`) are the entries of the decoded id table whose **names match the screens seen**; that binding is authored, not measured. * **A screen change is a fade through black.** Each screen carries a full-screen black `.prm` quad that paints last (`pteff00.prm` / `pfeff00.prm`) whose keyframe group *is* the transition: black at `T0`, clear by `T1`, clear until `T2`, then back to black on exit. ✅ decoded, with a disc-wide check — and in `GP_TITLE` exactly the six **screen** builds carry it while the six overlays do not. The fade-in length is `T1 − T0` and is read from the file (0.87 s for `EXTRAS`, 0.97 s main menu, 4.08 s title). ❔ **The fade-OUT length is not on the disc** — the last keyframe has no time slot; **measured ~0.4 s**, twice. The black hold measures 0.17–0.23 s. ⚠️ Do not time the fade-in off a capture's brightness: the incoming screen's own element animations dominate it and run much longer than the quad. * **The GamePart id table** — 29 entries at `.rdata 0x820A1630`, confirmed by the executable's own registration strings. ✅ This is the screen vocabulary; which button reaches which entry is Q4 and is *not* part of it. * **The logo splash is a screen, not a video.** `logo1`–`logo4` are manifest-bound with no `.wmv` on the disc. ✅ * **Sprites carry their own labels.** No font rendering or localisation is needed for this milestone. ✅ — and the localisation is *already baked in*: the Japanese screens are separate builds in the same pak, not a text swap. ## Facts the port will trip over * **`ADV.wmv` is WMV3 video + WMA Pro audio**, 1280×720 at 30 fps, 137 s. Godot 4 plays only Ogg Theora natively. How to handle that is the port's decision, not ours — but it is not optional. * **The disc holds 3.3 GB of video.** Only the boot intro and the one new-game intro are in scope. * **`Static.slb` over-declares its size** by 616 768 bytes — it is the highest-offset entry in `sound.pak` and its size field is an allocation size. A reader must allow a short read there and only there. * **Voice downmixes to mono, music does not.** The left-channel downmix is correct for spoken lines and discards half a music mix. * **A music bank has several sub-waves and we glue them together.** `BGM_001` is 10 KB + 4.47 MB + 4.67 MB, concatenated into one 347 s track. Nobody has established whether those are intro + loop, two variations, or two halves — see Q10. Do not build menu looping on the concatenated track until it is answered. * **`JNGL_001.slb` does not decode.** One bank in 9 519; its payload is not a whole number of XMA1 packets from any known data offset. ## Reference data Committed alongside the findings, so the port can be built without a disc in the loop during development: * `sylpheed-cli screen info --build GP_TITLE.pak` — the element table, per build, with pivots, kinds, focus links, keyframes and resting poses. * `sylpheed-cli screen render` — the reference composite. When the port draws a screen, this is what it should be diffed against; where they disagree, one of them is wrong and the disagreement is worth reporting back. * `docs/re/captures/` — framebuffer captures of the real screens, for anything that has to be checked against the game rather than against our renderer.