# What happens between two screens — a fade through black, and where its timing lives **Status:** ✅ `CONFIRMED`. The quad and its ramp are **decoded** (a keyframe group on the disc, with a disc-wide check); the wall-clock timings are **measured** off the running game at 30 fps. One piece is **undecodable from this field** and is called out below. Answers [MISSION Q7](../port/MISSION.md). ## The mechanism — decoded Every title-side screen carries a full-screen untextured primitive that is **black, and paints last**: `pteff00.prm` in `GP_TITLE`, `pfeff00.prm` in `GP_SAVE_LOAD`. That it sorts last was already established ([`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md)); what is new here is that its **keyframe group is the transition**. The group is always four blocks, and always this shape: | block | alpha | meaning | |---|---|---| | 1 | `0xff` at `t = T0` | the screen starts **black** | | 2 | `0x00` at `t = T1` | ramp to fully clear — the screen **fades in** | | 3 | `0x00` at `t = T2` | clear; this is the resting pose, the quad is invisible | | 4 | `0xff`, **no time** | ramp back to black — the screen **fades out** on exit | Read with the corpus's rule that a keyframe is the *start* of a ramp ([`structures/ui-resting-pose.md`](structures/ui-resting-pose.md)). ``` $ tools/re-capture/fade_quads.py 4 5 6 # GP_TITLE build 4 (title) pteff00.prm t=16 α=255 t=261 α=0 t=269 α=0 t=— α=255 build 5 (main menu) pteff00.prm t=12 α=255 t= 70 α=0 t= 80 α=0 t=— α=255 build 6 (EXTRAS) pteff00.prm t=12 α=255 t= 64 α=0 t= 74 α=0 t=— α=255 ``` Under [Q1](ui-keyframe-time-unit.md)'s `1 unit = 1/60 s`: the screen holds black for **0.20 s**, then fades in over **0.87 s** (`EXTRAS`), **0.97 s** (main menu) or **4.08 s** (the title). ### ❔ The fade-OUT duration is not in this field The fourth block has **no time** — a group's last block stops 4 bytes short and that word is already the next group's element index ([`ui_layout.rs`](../../crates/sylpheed-formats/src/ui_layout.rs) documents the packing). So the disc gives the ramp's *target* (black) and not its length. That duration is **measured** below, and the port is authoring it. ### The disc-wide check, and what it shows about overlays Over every `GP_*.pak`, counting bundles that have a `.prm` element **and** ≥8 elements (screen-sized rather than a two-element fragment): > **40 of 97** carry a 255 → 0 → 255 quad; 56 of the 60 found disc-wide have > exactly 4 keyframes. 41 % sounds weak until it is read per pak, where it is nearly all-or-nothing: | pak | with quad / screen-sized | |---|---| | `GP_MISSION_SELECT`, `GP_MOVIE_THEATER`, `GP_OPTIONS`, `GP_TUTORIAL`, `GP_SYSTEM` | 2/2 each | | `GP_BUNK`, `GP_CHALLENGE` | 6/6 | | `GP_STAGE_CLEAR` | 4/4 | | `GP_SAVE_LOAD` | 10/12 | | **`GP_TITLE`** | **6/12** | | `GP_DIALOG` | 0/133 | | `GP_DEBRIEFING_PILOTLOG` | 4/102 | **`GP_TITLE`'s 6 of 12 is the interesting row, and it is not a gap.** The six that carry the quad are exactly the six *screen* builds — title, main menu and `EXTRAS`, English and Japanese. The six that do not are exactly the six **overlays**: the `PRESS Ⓐ BUTTON` plate and the two `DELTASABER` plates ([`ui-title-build-map.md`](ui-title-build-map.md)). An overlay composited onto a screen has no transition of its own, so it has no fade quad — which is independent corroboration that those builds are overlays rather than screens. `GP_DIALOG`'s 0/133 says the same thing about dialog boxes. ## The timing — measured Recorded with `ffmpeg -f x11grab -framerate 30` over the game surface, mean frame luminance per frame; raw data in [`captures/transitions/transition-luminance.csv`](captures/transitions/transition-luminance.csv), filmstrip in [`transition-filmstrip.png`](captures/transitions/transition-filmstrip.png). | | main menu → `EXTRAS` (Ⓐ) | `EXTRAS` → main menu (Ⓑ) | |---|---|---| | press → first visible change | 0.07 s | 0.37 s | | **fade-out to black** | **0.367 s** | **0.400 s** | | **pure black** (luminance 0.02) | 0.233 s | 0.167 s | | luminance rise until settled | 0.567 s | 1.467 s | The fade-out is the number the file cannot give, and it comes out the same both ways: **~0.4 s**, i.e. ~24 units under Q1's rule. The black hold is *consistent with* the file's 12 units (0.20 s) but does not confirm it — the plateau spans the tail of the outgoing screen's fade-out and the head of the incoming screen's black, and this measurement cannot separate them. ### ⚠️ The luminance rise is **not** the quad's ramp The two columns differ by 2.6× where the quad's declared ramps differ by only 1.12× (58 units vs 52). The filmstrip says why: the background reappears *first* and the labels arrive after it, so what the luminance curve is timing is the incoming screen's **own element animations**, not the fade quad. Quoting 1.47 s as "the main menu's fade" would be wrong. The quad's ramp is decoded; the screen's build-in is a separate, longer thing. ## For the port * a screen change is: **fade the outgoing screen to black over ~0.4 s**, hold black briefly, then **fade the incoming screen in over its own declared ramp** while its elements play their own keyframes; * the fade-in ramp is **read from the file** (`T1 − T0` on the screen's fade quad); * the ~0.4 s fade-out and the black hold are **authored from this page** — the disc does not carry them. ## ✅ Which quantity the ~0.4 s is — measured 2026-08-29 Asked by the port: is 0.4 s **(a)** the ramp from the hold to the exit pose, i.e. exactly the missing duration of that untimed keyframe, **(b)** several keyframes' worth, or **(c)** something the game does independently of the group? **It is (a)** — and it is bigger than the fade quad. Two facts. **1. There is exactly one untimed keyframe, and every element has it.** Reading `screen info --build 5 --geometry` for the main menu, all 16 elements end on a single timeless block; none has two. So there is one unknown duration per screen, not a chain of them — which rules out (b) outright. And that final block is not idle: it is where the screen *plays out*. | elements | final untimed block | what it does | |---|---|---| | `pteff00.prm` (the fade quad) | `a = 255` | goes **black** | | `pteff10`, `pteff12`, `ptbtn01…05`, `ptmsg` | `a = 0` | **fade out** | | `ptframe1`, `ptframe2` | `a = 255` | hold, and get covered | | `ptbase`, `pteff05`, `ptloop*`, `pteff02.prm` | single keyframe | hold | **2. The capture shows the content fading, not just a black quad arriving.** This has a null hypothesis that discriminates: under (c) — the game blackens the frame independently — every region is scaled by the same `1 − α`, so the **ratio** between a button region and a background region is *constant* through the fade-out. Under (a) it must fall, because the buttons ramp to `a = 0` while the background elements hold at 255 and are only dimmed. Measured on [`transition-filmstrip.png`](captures/transitions/transition-filmstrip.png), button column ÷ upper-right background art, frame by frame through the fade-out: ``` frame 0 1 2 3 4 5 ratio 6.495 5.574 3.105 2.125 1.935 (black) ``` **A 3.4× monotonic fall.** Constant is refuted. The buttons really are fading independently of the overall dim, exactly as their declared final block says. (The incoming screen runs it in reverse, 2.22 → 3.47 over frames 7–12.) ⚠️ **Reach.** The filmstrip is downsampled and the "button" region unavoidably contains some background, so the ratio is a direction, not a clean alpha measurement. It refutes the constant-ratio null decisively; it does not by itself pin the 0.4 s to ±0.05 s. And it is measured on **one** transition pair. ### For the port Write **one** authored constant — the duration of the final untimed keyframe, ~0.4 s / ~24 units — and **play the group to its end on every element**. Do not model the exit as a black rectangle fading over a frozen screen: the buttons and labels ramp to transparent at the same time, and that difference is visible.