re(ui): replicate the keyframe-time shift -- three elements, two screens

The case for reading +36 as "the time the NEXT pose is reached" rested on
one element's fade-out shape, then on one element's hold duration. Both
splash halves supply more, and they agree.

  element            screen              observed hold  as decoded  shifted
  palogo_gamearts    developer splash        83 f          8 f       80 f
  palogo_seta        developer splash        83 f          6 f       80 f
  palogo_sqex        publisher splash       >=77 f *       6 f      102 f

  * the capture opens mid-hold at frame 1, so 77 is a floor.

The readings predict opposite structures. For palogo_gamearts, as decoded:
hold 8f, in 80f, hold 2f, out 6f, out 2f -- an eighty-frame FADE-IN and a
two-frame hold. Shifted: in 8f, hold 80f, out 2f, out 6f, out 2f. The
capture shows an 83-frame hold and no fade-in at all.

The elements that cannot discriminate are not contradicted:
palogo_gamearts_eff observed in 7f / hold 7f / out 8f, and both readings
give 8f phases -- with four blocks the shift only relabels which phase is
which. So the glows, which is where Q1's linear law was measured, say
nothing either way rather than arguing against.

The decoder's default is still unchanged, and the reason is now
articulated rather than assumed. The single thing opposing the shift is
rest() on ptlogo_eff3, where the shifted reading makes the longest-dwell
fallback return the bloom's 200% peak. That fallback is unsound whenever
it runs -- it returns an endpoint of a movement, neither of which is held
-- and checked: the shift does not fix it either. So the objection was
never evidence about the times. Timing had three discriminating
measurements; pose selection had a heuristic guessing.

For the port: animation timing should use the shift; static composites are
unaffected and the five screens' correlations stand. Classified measured,
not decoded -- three elements in one screen family, not a disc-wide check.
This commit is contained in:
Sylpheed RE agent
2026-08-29 04:09:36 +00:00
parent ceeb759841
commit 4bdb830671
4 changed files with 72 additions and 1 deletions

View File

@@ -27,7 +27,7 @@ authored version can be deleted.
| | Question | State | Answer / link |
|---|---|---|---|
| Q1 | keyframe time unit + ramp shape | ✅ answered, 🟡 one gap | ramp is **linear**; **2 units per rendered frame**; **`1 unit = 1/60 s` — settled**, the idle title presents at 28.5 fps so the game is 30 Hz. 🟡 **The interpolation law is settled; the group TIMELINE for multi-keyframe elements is not**`palogo_gamearts` is still at full alpha 9 frames after its declared `a=32`, and its declared 80-frame fade-in never draws — [`ui-keyframe-time-unit.md`](../re/ui-keyframe-time-unit.md) |
| Q1 | keyframe time unit + ramp shape | ✅ answered, 🟡 one gap | ramp is **linear**; **2 units per rendered frame**; **`1 unit = 1/60 s` — settled**, the idle title presents at 28.5 fps so the game is 30 Hz. 🟡 **The interpolation law is settled; the group TIMELINE for multi-keyframe elements is not**`palogo_gamearts` is still at full alpha 9 frames after its declared `a=32`, and its declared 80-frame fade-in never draws — [`ui-keyframe-time-unit.md`](../re/ui-keyframe-time-unit.md). ✅ **REPLICATED 2026-08-29 — for ANIMATION, read `+36` as the time the NEXT pose is reached.** Three elements across two screens: `palogo_gamearts` and `palogo_seta` hold full alpha for **83 frames** and `palogo_sqex` for **≥77**, where the current reading predicts **68** and the shifted one **80102**. The elements that cannot discriminate (the `_eff` glows, on which the linear law was measured) fit both. ⚠️ Our decoder still defaults to the other reading (`SYLPHEED_KF_TIME_SHIFT=1` to flip) because it changes `rest()` on one element — but that is an unsound fallback guessing either way, so **static rendering is unaffected and animation timing should use the shift** |
| 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, ❔ tie-break | **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). ⚠️ **The key does not fully order a screen**: elements sharing a key are tied, and the tie-break is ❔ **undecodable from the bundle** — declaration table, `T8aD` header (exhaustive: every offset 0x000x7f at u8/u16/u32, both directions, **0** fields match the measured order against **64** for the control) and the RATC child order all give the same order the game does *not* use. Your exposure is **2 overlapping tied pairs on `EXTRAS`** — [`structures/ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) |
| Q4 | button → GamePart | ✅ answered | **measured** which screen all **5** buttons open — `NEW GAME``DIFFICULTY``SELECT DATA`, not a hang. The **GamePart id is still a name match**, not a measurement — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md) |

View File

@@ -742,3 +742,11 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
inflated by my own duplicate runs. A slow thing observed under contention looks
like a stuck thing, and the correction matters: an hour-scale gate can be run
deliberately, a hung one cannot be run at all.
* **Separate the question with evidence from the question tangled with it.** The
keyframe-time reading was held back for several iterations by one objection:
adopting it changes `rest()` on a single element. But `rest()` reaches the times
only through a fallback that is unsound whenever it runs, and the shift does not
fix that fallback either — so the objection was never about the times. Timing
had three discriminating measurements; pose-selection had a heuristic guessing.
When a conclusion is blocked, check whether the blocker is actually evidence
about the same thing.

View File

@@ -522,3 +522,9 @@ neighbourhood, not just the line.
~4560 minutes. The 3 h 26 m observed was that work at a load average of 914,
inflated by my own two duplicate runs.
[`test-suite-runtime.md`](test-suite-runtime.md)
* "the case for the keyframe-time shift rests on a single element" → **no longer
true.** Three elements across two screens discriminate and all favour it:
`palogo_gamearts` and `palogo_seta` hold full alpha 83 frames, `palogo_sqex`
≥77, against 68 predicted by the current reading and 80102 by the shifted
one. The `_eff` glows fit both and argue against neither.
[`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md)

View File

@@ -333,3 +333,60 @@ of build 7 to verify the result against.
group's *timing* as unverified — and specifically should not expect a
2-frame hold where the game holds 83 — while `rest()` and `screen render` remain
as they are.
---
## ✅ Replicated: three elements, two screens, and the shifted reading wins every time
**2026-08-29.** The case for reading `+36` as *"the time the NEXT pose is
reached"* rested on one element's fade-out shape, then on one element's hold
duration. Both splash halves supply more, and they all say the same thing.
Phase durations in frames (2 units/frame, from the `_eff` glow ramp):
| element | screen | observed hold at `a=255` | as decoded | shifted |
|---|---|---|---|---|
| `palogo_gamearts` | developer splash | **83 f** | 8 f | **80 f** |
| `palogo_seta` | developer splash | **83 f** | 6 f | **80 f** |
| `palogo_sqex` | publisher splash | **≥ 77 f** * | 6 f | **102 f** |
\* the capture opens mid-hold at frame 1, so 77 is a floor, not the length.
The two readings predict **opposite structures** for these elements. On
`palogo_gamearts`, as decoded: `hold 8f, in 80f, hold 2f, out 6f, out 2f`
an eighty-frame *fade-in* and a two-frame hold. Shifted: `in 8f, hold 80f,
out 2f, out 6f, out 2f` — an eight-frame fade-in and an eighty-frame *hold*.
The capture shows a **83-frame hold** and no fade-in at all.
### And the elements that cannot discriminate are not contradicted
`palogo_gamearts_eff` observed `in 7f, hold 7f, out 8f`. As decoded it reads
`in 8f, hold 8f`; shifted it reads `hold 8f, out 8f`. Both fit — with only four
blocks the shift relabels which phase is which without changing any duration. So
the glows, which are what Q1's linear law was measured on, **do not argue against
the shift**; they simply say nothing about it.
### 🟡 Why the decoder's default is still unchanged
The one thing that opposes the shift is `Element::rest()` on `ptlogo_eff3.t32`,
where the shifted reading makes the longest-dwell fallback return the bloom's
200 % peak instead of an invisible frame. But that fallback is **unsound whenever
it runs** ([resting pose](structures/ui-resting-pose.md)) — it returns an
endpoint of a movement, and neither endpoint is held. Checked: the shift does not
fix it either, so this is not a case of two readings disagreeing about the times.
It is a heuristic guessing, in both.
**So the two questions are separable, and only one of them has evidence.** The
times govern animation; `rest()` picks a static pose through a rule that consults
them only after its own precondition has failed.
### ⚠️ What a port should do
For **animation timing**, read `+36` as the *next* pose's time: three elements
across two screens, each off by an order of magnitude under the other reading.
For **static composites**, nothing changes — `screen render` is unaffected, and
the five screens' correlations stand ([acceptance](five-screens-acceptance.md)).
🟡 Classified **measured, not decoded**: this is three elements in one game
screen family, not a disc-wide field check, and our own decoder still defaults to
the other reading behind `SYLPHEED_KF_TIME_SHIFT=1`.