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 27e8c51978 re: a keyframe time is worth half a rendered frame, and the ramp is linear
Q1 of the menu port, measured against the running game rather than reasoned
about. The developer-logo splash is the cheap target: it is the first thing the
guest draws and its bundle declares short, unambiguous ramps.

Two results, both frame-exact and both emulator-speed-independent (frame numbers
are VdSwap counts, the guest's own frames):

  * the ramp is LINEAR. A declared 15-unit fade lands on round(255*k/15) for all
    seven of its samples with zero error, k stepping 2,4,6,8,10,12,14. No ease
    can reproduce a constant step of 34 at both ends.
  * the animation clock advances 2.000 time units per submitted frame, over six
    consecutive intervals with no residual, with 1 unit as the quantum
    underneath (one frame in the fade-out advances by 1).

The conversion to seconds is one step further and is flagged as such: 300 frames
took 10.87 s = 27.6 present-frames/second, which reads as a 30 Hz title at 92 %
under the emulator and gives 1 unit = 1/60 s -- the title build 4.2 s, the main
menu build 1.1 s. That reading is not proven, because the rate was measured
while the guest was still streaming from the ISO; the page names the one test
that would settle it and says what changes if it goes the other way.

Committed beside it: the raw draw capture and the per-frame quad CSV, so the
numbers can be re-derived without a disc or an emulator.
2026-08-28 15:56:33 +00:00

110 lines
6.4 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.
# 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 | 🟡 partial | build 4 title, 5 main menu, 6/8/9 submenus, `palogo` splash — unconfirmed against captures |
| Q3 | paint order for the six screens | ❔ open | runtime-solved only; declaration table is refuted |
| Q4 | button → GamePart | ❔ open | labels are baked into sprites |
| Q5 | navigation semantics | ❔ open | |
| Q6 | boot sequence + what drives it | 🟡 partial | order observed; the driver is not decoded |
| Q7 | transitions | ❔ open | |
| 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 the whole title-side tree.** Build 4 is the title with the
animating wordmarks, build 5 the five-button main menu, builds 6/8/9 are
submenus, and the developer splash is the `palogo` bundle in the same archive.
✅ decoded (enumeration), 🟡 the state labels are not yet capture-confirmed.
* **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 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. ✅
## 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.