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/re/ui-keyframe-time-unit.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

132 lines
6.8 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.
# What a keyframe time is worth, and what shape the ramp has
**Status:**`CONFIRMED` for the two things the port is blocked on — the ramp is
**linear**, and the animation clock advances **2 keyframe time units per frame the
game submits**. 🟡 the conversion to *seconds* rests on one further step: the game
was measured presenting **27.6 frames/second**, which reads as a 30 Hz title
running at ~92 % under the emulator, giving **1 unit = 1/60 s**. That last step is
reasoning over a measurement, not a measurement — see *the reach of the negative*
below.
This answers `docs/port/MISSION.md` **Q1**. Everything animated downstream — the
title wordmark zoom, the main menu's staggered buttons, every fade — is scaled by
this number.
## The measurement
The question cannot be asked of a screen that is sitting still, and
`log_ui_draws` armed *at* a screen only ever sees the steady state. So arm it
repeatedly through the boot and keep every log: each F10 opens a new numbered
file and **closes** the previous one, which stays on disk complete. Re-arming
every 3 s tiles the whole approach to a screen, and whichever file straddles the
build contains it. `tools/re-capture/screen_build_capture.sh` does this; it sends
no pad input at all, because Ⓐ during the boot has ended a run on a permanent
black screen (`canary-scripted-input-traps.md`).
The developer-logo splash is the cheap target: it is the first thing the guest
draws, roughly ten seconds in, and its bundle (`GP_TITLE.pak`, `--all --build 11`
/ `14` — the `palogo` group) declares short, unambiguous ramps.
The capture is committed: [`captures/ui-timing/splash-build-draws.log`](captures/ui-timing/splash-build-draws.log)
(the raw draw stream) and [`captures/ui-timing/splash-build-quads.csv`](captures/ui-timing/splash-build-quads.csv)
(`tools/re-capture/kf_time_probe.py`, one row per quad per frame: pixel rect and
the per-vertex colour whose high byte is the element's fade).
Frame numbers are the emulator's **VdSwap count** — frames the guest submitted —
so an emulator running at 80 % of real time does not move them. That is the whole
reason to measure in this unit rather than with a stopwatch.
### The quads are the splash, by position and size
Every sprite in the capture lands on its declared placement:
| capture quad | declared element (build 11) | declared placement |
|---|---|---|
| `666x65 @ (307,331)` | `palogo_sqex.t32` 666×68 | (309,330) |
| `525x90 @ (378,155)` | `palogo_gamearts_eff.t32` 521×91 | (379,154) |
| `262x108 @ (512,306)` | `palogo_seta_eff.t32` 261×110 | (511,305) |
| `499x72 @ (390,162)` | `palogo_gamearts.t32` 500×71 | (390,164) |
| `243x86 @ (518,317)` | `palogo_seta.t32` 240×89 | (521,316) |
(A quad runs a few pixels under its sprite; that offset is already recorded in
`ui-title-paint-order-capture.md` and is not what is being measured here.)
## Result 1 — the ramp is linear, exactly
`palogo_gamearts_eff` and `palogo_seta_eff` declare a fade-in of
`t=15 a=0 → t=30 a=255`: a **15-unit** ramp. Their fade alpha, frame by frame,
straight out of the capture:
| frame | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
|---|---|---|---|---|---|---|---|---|
| alpha | `0x22` 34 | `0x44` 68 | `0x66` 102 | `0x88` 136 | `0xAA` 170 | `0xCC` 204 | `0xEE` 238 | `0xFF` 255 |
| ⇒ units into the ramp *k* | 2 | 4 | 6 | 8 | 10 | 12 | 14 | ≥15 |
`round(255·k/15) = 17k` reproduces **all seven samples with zero error**. An eased
ramp cannot do that: any ease-in/ease-out would bend the first and last steps, and
these are a constant 34 throughout. **Linear interpolation, refuted-nothing-left.**
The same element's fade-*out* gives the same law with a one-count offset —
`255·(1k/15) 1` = 254, 220, 186, 152, 118, 84, 67, 33 for
*k* = 0, 2, 4, 6, 8, 10, 11, 13, which is exactly what the capture holds on frames
108115.
## Result 2 — 2 time units per submitted frame
Read *k* off the row above: 2, 4, 6, 8, 10, 12, 14 on seven consecutive submitted
frames. **The clock advances 2.000 units per frame, over six consecutive
intervals, with no residual.** The fade-out shows the same 2-per-frame step with
one single-unit frame (*k* goes 10 → 11 → 13), so the quantum underneath is 1 unit
and the normal step is two of them.
Cross-checks in the same capture, all consistent:
* the declared hold `t=30 → t=45` (15 units) is held for frames 101107, **7
frames** ≈ 14 units;
* `palogo_sqex`'s fade-out steps in multiples of 17 per frame (34 with the odd
17), the same 255/15 quantum;
* the `gamearts`/`seta` pair holds full alpha for frames 116198 and fades out
over frames 199211.
## Result 3 — the conversion to seconds, and how far it reaches
300 submitted frames took **10.87 s** wall-clock, measured by arming one bounded
capture over the splash and timing it to its own `[UI-CAP] done` line:
**27.6 frames/second**.
With 2 units per frame that is **55 units/second measured**. The two readings that
fit are:
* **30 Hz present, 60-unit-per-second timeline** — the emulator running at 92 % of
real time. A 30 Hz renderer stepping a 60 Hz timeline is exactly the 2-units-
per-frame quantum that was measured, and 92 % is an ordinary number for this
container.
* 60 Hz present at 46 % of real time, giving a 120-unit-per-second timeline. This
requires the emulator to be running at *less than half speed* while drawing four
quads over a black screen, and it requires the game's timeline to tick at 120 Hz.
**Taking the first**: `1 unit = 1/60 s`. The title build (`GP_TITLE.pak` build 4,
t = 16 … 269) is then **4.2 s**, and the main menu build (build 5, t = 12 … 80)
**1.1 s**.
**The reach of this negative:** the present rate was measured *during the boot
splash*, where the guest is also streaming from the ISO, so it is a lower bound on
the emulator's speed and cannot by itself exclude the 60 Hz reading. What would
settle it is the same 300-frame timing taken on the **idle title screen**, where
nothing is loading — if that also comes out near 28 fps the game is 30 Hz and the
unit is 1/60 s; if it doubles to ~55 fps the game is 60 Hz and every duration on
this page halves. Until then the port should treat **2 units per rendered frame**
as the measured fact and `1/60 s` as the working conversion.
## What this does not say
* Nothing here is a *decode*: no field on the disc says "sixtieths of a second".
The number is measured from the running game, and the port is transcribing a
measurement, not a disc value.
* The splash drew `palogo_sqex`, then `palogo_gamearts` + `palogo_seta` with their
`_eff` sprites — but **not** `palogo_anima` (declared at 388×136 @ (446,449)) and
not `palogo_sqex_eff`, in the ~7 s of capture after the pair faded out. The
`_sqex_eff` absence is explainable (the capture joined the SQUARE ENIX logo
mid-hold); the missing studio-anima logo is not, and is left as an open
observation for Q2 rather than a claim.