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

6.8 KiB
Raw Permalink Blame History

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 (the raw draw stream) and 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.