its timing is on the disc Q7. Every title-side screen carries a full-screen black .prm quad that paints last, and its keyframe group IS the transition: black at T0, clear by T1, clear until T2, then back to black on exit. Read with the corpus's start-of-a-ramp rule and Q1's time unit that gives 0.87s for EXTRAS, 0.97s for the main menu, 4.08s for the title -- from the file, not from a stopwatch. The disc-wide check is per-pak all-or-nothing rather than the 41% the headline count suggests, and GP_TITLE's 6 of 12 is the useful row: the six builds carrying a fade quad are exactly the six SCREENS, and the six without are exactly the six overlays. GP_DIALOG is 0 of 133. That is independent corroboration of the overlay finding from two iterations ago. One piece is NOT on the disc and says so: the fade-OUT length. The fourth keyframe has no time slot, because a group's last block stops four bytes short. Measured instead, at 30fps, ~0.4s and the same both directions. And a warning I earned: the luminance rise after a transition is NOT the quad's ramp. The incoming screen's own elements animate in after the quad has cleared -- 1.47s observed against a declared 0.97s. Time the fade from where the frame is pure black. Rig: screenshot samples at 0.5 Hz and cannot see a 0.4s fade at all, which is why an earlier burst called this an instant cut. ffmpeg x11grab at 30fps instead; both go in METHOD.
118 lines
5.4 KiB
Markdown
118 lines
5.4 KiB
Markdown
# 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.
|