This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/port/HANDOFF.md
Sylpheed RE agent 7fef19b3a0 re: the transition between screens is a fade through black, and most of
its timing is on the disc

Q7. Every title-side screen carries a full-screen black .prm quad that
paints last, and its keyframe group IS the transition: black at T0, clear
by T1, clear until T2, then back to black on exit. Read with the corpus's
start-of-a-ramp rule and Q1's time unit that gives 0.87s for EXTRAS,
0.97s for the main menu, 4.08s for the title -- from the file, not from a
stopwatch.

The disc-wide check is per-pak all-or-nothing rather than the 41% the
headline count suggests, and GP_TITLE's 6 of 12 is the useful row: the
six builds carrying a fade quad are exactly the six SCREENS, and the six
without are exactly the six overlays. GP_DIALOG is 0 of 133. That is
independent corroboration of the overlay finding from two iterations ago.

One piece is NOT on the disc and says so: the fade-OUT length. The fourth
keyframe has no time slot, because a group's last block stops four bytes
short. Measured instead, at 30fps, ~0.4s and the same both directions.

And a warning I earned: the luminance rise after a transition is NOT the
quad's ramp. The incoming screen's own elements animate in after the quad
has cleared -- 1.47s observed against a declared 0.97s. Time the fade
from where the frame is pure black.

Rig: screenshot samples at 0.5 Hz and cannot see a 0.4s fade at all,
which is why an earlier burst called this an instant cut. ffmpeg x11grab
at 30fps instead; both go in METHOD.
2026-08-28 18:05:19 +00:00

163 lines
12 KiB
Markdown
Raw 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.
# 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**: ~810 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.170.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 <n> 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.