# 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ยท(1โˆ’k/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 108โ€“115. ## 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 101โ€“107, **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 116โ€“198 and fades out over frames 199โ€“211. ## 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** *(as written before the test below โ€” kept for the reasoning)*: 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. ## โœ… That test was run โ€” 2026-08-28. The game is 30 Hz; `1 unit = 1/60 s` Reached the title with a single โ’ถ to skip the intro ([`movie-binding.md`](movie-binding.md)), let it settle 12 s so nothing was loading, then armed the bounded 300-frame capture and timed it to its own `[UI-CAP] done` line: | | 300 frames in | rate | |---|---|---| | idle title, trial 1 | 10.40 s | **28.8 fps** | | idle title, trial 2 | *arm produced no capture* | โ€” | | idle title, trial 3 | 10.60 s | **28.3 fps** | | *(prior)* boot splash | 10.87 s | 27.6 fps | **~28.5 fps on an idle title โ€” the same rate as the loading splash.** By this page's own criterion that settles it: the title presents at **30 Hz**, the timeline ticks at **60 units/second**, and **`1 unit = 1/60 s`**. **The 60 Hz reading is now excluded, not merely disfavoured.** It would require the emulator to be running at 47 % of real time *while sitting idle on a static title* โ€” and the capture says that screen costs **1 526 draws over 300 frames, about 5 draws per frame**. Nothing there is expensive enough to halve the emulator's speed, and the splash and the idle title returning the same rate is exactly what a constant ~95 %-of-real-time emulator looks like. So the durations on this page stand as written: title build 4 โ‰ˆ **4.2 s**, main menu build 5 โ‰ˆ **1.1 s**, and `EXTRAS` build 6's declared fade-in โ‰ˆ **0.87 s**. โš ๏ธ Still **measured, not decoded** โ€” no field on the disc says "sixtieths of a second". What changed is that the measurement now has an idle-state control and the competing reading is ruled out. ## 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.