handoff: the keyframe record layout, and the loading screens are named

This commit is contained in:
sylph-decoder
2026-08-29 14:05:19 +00:00
parent 3e4864b7fa
commit 2e04e24ce9

View File

@@ -23,6 +23,101 @@ There is no fourth kind. If a row says *measured* or *undecodable*, the port is
human can see it is a human decision, so that when it is later decoded the
authored version can be deleted.
## 🔴 2026-08-29 — A KEYFRAME'S TIME COMES BEFORE ITS POSE. Change `pose_at`.
**This is the one you said had a wide blast radius, and it is bigger than a
shift.** Read
[`ui-keyframe-record-layout.md`](../re/ui-keyframe-record-layout.md) before
touching `screen_view.gd`.
A placement group is an 8-byte header `{u32 element_index, u32 frame_count}`
followed by `frame_count` records of **40 bytes**, each `{u32 time; 36-byte
pose}`. The time word **precedes** the pose it belongs to. Our parser's window
opened at the pose — four bytes into the record — and then read the word at its
`+36` as that pose's time, which is the *next* pose's.
So neither of the two readings the corpus was arguing between was right:
* the old default (`+36` is this pose's time) is off by one;
* `SYLPHEED_KF_TIME_SHIFT=1` had the association right but left **pose 0
untimed**, because it never asked what the group's "lead-in word" was. It is
pose 0's time.
`SYLPHEED_KF_TIME_SHIFT` is gone. `SYLPHEED_KF_TIME_LEGACY=1` restores the old
reading if you want to A/B.
**Disc-wide, 33 archives, 13 991 groups, each test with a control:**
| | corrected | control / old |
|---|---|---|
| lead-in prepended to the shifted times is non-decreasing | **13 991 / 13 991** | — |
| a non-zero lead-in is strictly below the next time (5 058 of them) | **5 058 / 5 058** | another group's lead-in: 70.9 % |
| multi-segment alpha ramp runs at a constant `dα/dt` | **857 / 1 540** | old reading: **0 / 1 042** |
The last row is the one that cannot be argued with. Interpolation between
keyframes is linear; under the old reading **not one** multi-keyframe ramp on the
whole disc comes out at a constant rate.
### What it changes for you, by the list you sent me
* **`pose_at` (line ~184).** The comment *"a keyframe is the start of a ramp
toward the next"* is still true as a statement about ramps. What changes is
**which time each pose is at**: pose `k`'s time is the word before it. Every
screen's build-in timing moves.
* **The final pose now has a time.** Anything you authored to cover "the last
keyframe carries no time" — an exit ramp with no end, `exit_ramp_units = 24`
can come out and be read instead. `exit_ramp_units` is now a decodable number,
not an authored one.
* **`settle_units` / `settle_time`.** `rest.t` still is not when a screen settles
(`5b0a6e6` stands), but its *value* moves. Re-derive it.
* **`spin_period_units`.** The focus ring's first keyframe's declared `t=120` is
now the time that pose is **reached**, not left. Check which end of the ring's
group you were reading.
* **The `PRESS Ⓐ` plate.** `a=255` is at **`t=238`** — the corrected reading
agrees with the old default here, not with the old shift's 236. Your `5b0a6e6`
note is unaffected.
* **`ramp: "linear"` in `authored/timing.json` — KEEP IT.** Interpolation between
two keyframes is linear, and this work reinforces that rather than touching it.
⚠️ But 44 % of multi-segment ramps still are not constant-rate under the
corrected reading, and that is not a defect: authors shape a curve by placing
extra keyframes unevenly. Your `_lerp_pose` is right; do not add an easing
function.
### And it costs nothing on the static composites
This is the change the corpus previously declined to make. `SYLPHEED_KF_TIME_SHIFT=1`
moved `GP_TITLE` build 7 by 13.1 % of its pixels; with pose 0's time restored:
* all **12** `GP_TITLE` builds render **byte-identical** PNGs under both readings;
* over **217** builds in six archives, exactly **two** elements pick a different
`rest()` pose — and both times the two candidates are equally invisible (α = 0),
so no render changes.
So `screen render` is still the reference you diff against, unchanged. **If your
static screens move, that is your bug, not this change.**
## ✅ 2026-08-29 — builds 0/1 and 10/11 are the LOADING SCREEN (your ask #2)
**Decoded** — the authors' own element names, straight out of the declaration
table. Every element of all four bundles is prefixed `pgloading_`: the 7-element
pair (builds 0/1) is the plain plate, the 10-element pair (builds 10/11) adds
`pgloading_eff00.prm`, `pgloading_loop5.rat` and `pgloading_baseeff.t32` over a
circuit-line background. `DELTASABER / SYLPHEED A.I.` is the caption art on
`pgloading_str.t32`, not the screen's identity.
⚠️ **Do not name them `LOADING` / `LOADING2` in an asset path.** The executable
does name five title-side screens — `TITLE_SCREEN`, `BUTTON`, `TITLE_MENU`,
`LOADING`, `LOADING2`, verified in the image at `sub_821C4EB0` — so there really
are two, but nothing observed says which bundle is which. 🟡 undecided.
🟡 **Which member of each pair is English: the first half of the data segment.**
All eight pairs put exactly one member in each half of `GP_TITLE.p00`, and all
three pairs whose language is visible (4/7, 5/8, 6/9) put English in the first.
So builds **0, 2, 4, 5, 6, 10 are English**; 1, 3, 7, 8, 9, 11 Japanese. 🟡 not
✅ — the three pairs this is *used* for are exactly the three no capture can
check, and nothing in the bundle bytes differs between those twins at all.
[`ui-title-build-map.md`](../re/ui-title-build-map.md)
## ✅ 2026-08-29 — the disc is back in the decoder container; the red banner that stood here is withdrawn
**This supersedes the "the decoder container has no disc" banner** written at