# Which `GP_TITLE` build is which screen β€” measured against the running game **Status:** βœ… `CONFIRMED` for the four screens the boot path actually shows (title art, `PRESS β’Ά BUTTON`, main menu, `EXTRAS`); 🟑 `PROBABLE` for their Japanese twins. βœ… **2026-08-29 β€” the two "unidentified `DELTASABER` plates" are the LOADING SCREEN**, and the ❔ on them is withdrawn. See [below](#-the-deltasaber-plates-are-the-loading-screen-and-there-are-two-of-them). Answers [MISSION Q2](../port/MISSION.md). The previous statement β€” *"build 4 title, 5 main menu, 6/8/9 submenus"* β€” is **partly wrong** and is withdrawn: build 8 is the **Japanese main menu**, not a submenu, and `GP_TITLE` holds exactly **one** submenu (`EXTRAS`), in two languages. ## The archive is eight screens, each shipped twice `GP_TITLE.pak` has 16 entries; `sylpheed-cli screen list` calls 12 of them composable builds. The 16 fall into **eight pairs**, and each pair's two members have near-identical sizes and name hashes that differ by a constant β€” one character of the name apart: | entry | hash | bytes | build idx | pair | |---|---|---|---|---| | 0 | `01a2db9c` | 483 958 | 0 | A | | 1 | `0ff0b8a8` | 483 958 | 1 | A | | 2 | `285d8849` | 267 014 | 2 | B | | 3 | `369773cb` | 267 014 | 3 | B | | 4 | `a60fcb85` | 12 278 666 | 4 | C | | 7 | `b483e6e6` | 13 363 328 | 7 | C | | 5 | `a715f485` | 6 977 437 | 5 | D | | 8 | `b58a0fe6` | 6 931 653 | 8 | D | | 6 | `a81c1d85` | 6 549 126 | 6 | E | | 9 | `b69038e6` | 6 548 438 | 9 | E | | 10 | `cdba806e` | 426 473 | β€” | F | | 13 | `db2ea1f8` | 423 333 | β€” | F | | 11 | `cec0a96e` | 999 643 | β€” | G | | 14 | `dc34caf8` | 999 643 | β€” | G | | 12 | `cf2a8ccd` | 1 774 639 | 10 | H | | 15 | `dd56d99b` | 1 774 639 | 11 | H | For the three pairs whose members differ visibly β€” C, D, E β€” **the difference is English versus Japanese**: build 7 is the title art with the katakana subtitle γƒ—γƒ­γ‚Έγ‚§γ‚―γƒˆ シルフィード, build 8 the main menu reading 新規 / ロード / チγƒ₯γƒΌγƒˆγƒͺをル / γ‚ͺプション / γ‚¨γ‚―γ‚Ήγƒˆγƒ©, build 9 the `EXTRAS` submenu as γ‚¨γ‚―γ‚Ήγƒˆγƒ©. That is the whole of the language split we can see. In pairs A, B and H the two members render **byte-identical PNGs** β€” the archive still carries two copies, but the artwork does not change with the language. Pairs F and G are the four entries `screen list` does *not* classify as builds by default. **They are the developer splash**, and they render β€” see below. ## The map Contact sheet of every render: [`captures/title-builds/title-build-contact-sheet.png`](captures/title-builds/title-build-contact-sheet.png). | build | what it is | confirmed how | |---|---|---| | 0, 1 | βœ… **the LOADING screen**, plain β€” a `DELTASABER / SYLPHEED A.I.` plate low-left on black, no background. 7 elements, all named `pgloading_*` | βœ… **decoded from the declaration table**, not observed running | | **2**, 3 | the `PRESS β’Ά BUTTON` plate β€” **an overlay build of its own**, not a state of build 4 | βœ… seen composited over build 4 on the live title, at the same rect our render puts it | | **4** | title art, English (`PROJECT SYLPHEED`, β„’, `(C)2006,2007 SQUARE ENIX`) | βœ… [`live-title-press-a.png`](captures/title-builds/live-title-press-a.png) | | 7 | the same, Japanese | 🟑 renders as the JP twin of build 4; the container runs an English locale, so it was not seen | | **5** | main menu, English: NEW GAME / LOAD GAME / TUTORIAL / OPTIONS / EXTRAS, footer `β’Ά : OK` | βœ… [`live-main-menu.png`](captures/title-builds/live-main-menu.png) β€” element for element | | 8 | the same, Japanese | 🟑 as above | | **6** | the `EXTRAS` submenu, English: MISSION SELECT / MOVIE THEATER / BACK, footer `β’Ά : OK β’· : Back` | βœ… [`live-extras.png`](captures/title-builds/live-extras.png) | | 9 | the same, Japanese | 🟑 as above | | 10, 11 | βœ… **the LOADING screen**, dressed β€” the same plate over a dark circuit-line background. 10 elements, the same seven plus `pgloading_eff00.prm`, `pgloading_loop5.rat` and `pgloading_baseeff.t32` | βœ… **decoded from the declaration table**, not observed running | ## βœ… The `DELTASABER` plates are the LOADING screen, and there are two of them **2026-08-29. Decoded β€” the authors' own element names, out of the declaration table.** No renderer, no capture, no inference. Every element of all four bundles is prefixed `pgloading_`: | build | entry | elements | |---|---|---| | 0, 1 | 0, 1 | `pgloading_loop1.rat` `pgloading_loop3.rat` `pgloading_str.t32` `pgloading_line.t32` `pgloading_loop4.rat` `pgloading_eff01.t32` `pgloading_eff02.t32` | | 10, 11 | 12, 15 | the same seven, plus `pgloading_eff00.prm` `pgloading_loop5.rat` `pgloading_baseeff.t32` | Reproduce: ```bash sylpheed-cli screen info --build 0 "$SYLPHEED_DISC/dat/GP_TITLE.pak" sylpheed-cli screen info --build 10 "$SYLPHEED_DISC/dat/GP_TITLE.pak" ``` `DELTASABER / SYLPHEED A.I.` is the artwork on `pgloading_str.t32` β€” the loading screen's caption, not the screen's identity. Reading the picture named the plate; reading the file names the screen. ⚠️ This also removes the reason the pair was open. The previous row said *"never seen in the boot path, the main menu, `EXTRAS` or `MISSION SELECT`… a mission load is the remaining candidate"*. It is a **loading** screen: it is not supposed to appear on any of those, and the remaining candidate was right. ### 🟑 Which is `LOADING` and which is `LOADING2` β€” the executable names five `sub_821C4EB0` builds `GamePart_Title` and asks its table for five sub-entries in this order, one `bl sub_821CEDF8` each, setting an error flag on any failure: | call site | string | VA | |---|---|---| | `0x821C503C` | `TITLE_SCREEN` | `0x820A3D3C` | | `0x821C5068` | `BUTTON` | `0x820A339C` | | `0x821C5090` | `TITLE_MENU` | `0x820A3D30` | | `0x821C50BC` | `LOADING` | `0x820A214C` | | `0x821C50E4` | `LOADING2` | `0x820A3D24` | βœ… **Checked against the image, not just the database** β€” `/image/sylpheed.pe` at `0x821C503C`, `0x821C5090`, `0x821C50BC`, `0x821C50E4` reads `38aa3d3c`, `3baa3d30`, `38aa214c`, `38aa3d24`, exactly the `addi rX, r10, ` the database shows, with `r10 = 0x820A0000` set two instructions earlier. That is five named title-side screens, and `BUTTON` is what the `PRESS β’Ά BUTTON` overlay would be called β€” which the corpus had already isolated as a build of its own (pair B) on capture evidence alone. 🟑 **Two loading screens named, two loading bundles found β€” but nothing observed maps one to the other.** `LOADING` is the 7-element plain plate and `LOADING2` the 10-element dressed one *if* the suffix means "the second, richer variant", and that is a guess about a name. The port should treat the pairing as **undecided** and not carry either name into an asset path. Also note the lookup is **not** by pak TOC hash: at `0x821C5118`–`0x821C512C` the game hashes `BASE_INFO` and `TITLE_MENU` with `sub_82455C78` (the same name-hash [`hash.rs`](../../crates/sylpheed-formats/src/hash.rs) implements) and splices the two 32-bit results into one 64-bit key. So these strings key a **sub-table inside the GamePart's own record**, not an archive entry β€” which is why `pak list` resolves none of `GP_TITLE`'s 16 names. ## 🟑 Which member of each pair is English β€” the archive is packed in two halves The port asked which member of pairs A, B and H is which locale, since those three render byte-identically and no capture can tell them apart. The **data segment** can: | | entries | data offset | |---|---|---| | first half | 0, 2, 4, 5, 6, 10, 11, 12 | 0 … 507 904 | | second half | 1, 3, 7, 8, 9, 13, 14, 15 | 6 078 464 … 10 868 736 | Every one of the eight pairs has **exactly one member in each half**, and in all three pairs whose language is visible β€” C (4/7), D (5/8), E (6/9) β€” the English build is the one in the **first** half. `GP_TITLE.p00` is one locale's eight bundles followed by the other's; the TOC interleaves them only because it is sorted by name hash. So: **first half = English**, i.e. builds 0, 2, 4, 5, 6, 10 are English and 1, 3, 7, 8, 9, 11 are Japanese. ⚠️ 🟑 not βœ…. The rule is 8/8 structurally consistent and 3/3 where it can be checked, but the three pairs it is *used* for are exactly the three it cannot be checked on. Reach of the negative: nothing in the bundle bytes themselves β€” the header, the declaration table, the element names β€” differs between the twins of pairs A, B and H at all; they render byte-identical PNGs. If a locale marker exists it is not in the bundle. ## βœ… The splash β€” the four "non-build" entries, rendered `screen list --all` widens the enumeration to every composable bundle and shows all **16** entries. The four that the default listing drops are the two halves of the developer splash, each shipped twice: | entry | elements / sprites | what it is | |---|---|---| | **10, 13** | 3 / 2 | the white **`SQUARE ENIX`** publisher logo | | **11, 14** | 7 / 6 | **`GAME ARTS` / `SETA` / `studio anima`** β€” the developer logos | [`splash-entries-rendered.png`](captures/title-builds/splash-entries-rendered.png). Rendered with `screen render --all --primitives --black`. Entry 11's seven elements are the three logos, their three `_eff` glows and the `palogo_eff0.prm` black backdrop β€” exactly the composition [`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md) measured for the splash. ### βœ… Confirmed against the running game β€” 2026-08-28 The 🟑 above ("no framebuffer capture to diff against") is closed. Recording the boot from the moment the window appears, at 2 fps, catches the splash before the movie: [`live-splash-publisher.png`](captures/title-builds/live-splash-publisher.png), [`live-splash-developer.png`](captures/title-builds/live-splash-developer.png). Edge-correlated against the renders, **with the other half as a negative control**: | capture | vs entry 10 | vs entry 11 | |---|---|---| | publisher frame (t β‰ˆ 2.0 s) | **0.9146** @ (0,0) | 0.027 βœ— | | developer frame (t β‰ˆ 6.0 s) | βˆ’0.033 βœ— | **0.9792** @ (0,0) | Both halves match their own render at zero shift and are firmly rejected by the other. The `13`/`14` twins score 0.876 / 0.966 β€” near-identical artwork, so this test cannot tell a pair apart, only a screen from a different screen. ### βœ… The splash's timing is DECODED, and the capture confirms it Re-recorded at **10 fps** (the 2 fps pass below was Β±0.5 s and could not see ramps at all). The splash **fades, both ways** β€” it does not cut β€” and the bundle's own keyframes say so: ``` $ sylpheed-cli screen info --all --build 10 GP_TITLE.pak 1 palogo_sqex.t32 7 kf [15 30 235 239 251 255 -] 2 palogo_sqex_eff.t32 4 kf [15 30 45 -] (the glow, child of 1) $ ... --build 11 1 palogo_gamearts.t32 7 kf [15 30 190 194 206 210 -] (seta, anima identical) 2 palogo_gamearts_eff 4 kf [15 30 45 -] ``` Under Q1's `1 unit = 1/60 s`: | | declared | measured at 10 fps | |---|---|---| | `SQUARE ENIX` ramp in | `15 β†’ 30` = 0.25 s | rise 0.4 β†’ 0.8 s | | `SQUARE ENIX` hold | `30 β†’ 235` = **3.42 s** | β‰ˆ 3.5 s (0.8 β†’ 4.3 s) | | `SQUARE ENIX` fade out | `235 β†’ 255` = **0.33 s** | **β‰ˆ 0.3 s** (4.4 β†’ 4.7 s) | | developer hold | `30 β†’ 190` = **2.67 s** | β‰ˆ 2.4 s (5.6 β†’ 8.0 s) | | developer fade out | `190 β†’ 210` = **0.33 s** | **β‰ˆ 0.3 s** (8.1 β†’ 8.4 s) | The offset between declared and measured start is β‰ˆ 0.35 s, which is simply that the recording's `t = 0` is when the *window* appears, not when the guest starts drawing. Everything downstream of that lines up. **The overshoot is the glow.** The measured rise peaks (6.21) at 0.8 s and settles back (5.39) by 1.1 s, which looks like a bloom. It is the `_eff` child element: its keyframes are `15 β†’ 30 β†’ 45`, so it ramps in *after* the logo and then back down, while the logo itself holds. Decoded, not a rendering artifact. So the port can **read** the splash's timing off the disc rather than author it β€” the first screen's animation is not a measurement it has to trust. ### The wall-clock sequence, for orientation | | | |---|---| | `SQUARE ENIX` on screen | **β‰ˆ 0.5 β†’ 4.0 s** after the window appears | | black | β‰ˆ 4.5 s | | `GAME ARTS` / `SETA` / `studio anima` | **β‰ˆ 5.0 β†’ 7.5 s** | | black | β‰ˆ 9.0 s | | `ADV.wmv` begins | **β‰ˆ 9.5 s** | ⚠️ The frames at β‰ˆ 9.5–12.5 s show `SQUARE ENIX` again in **cyan**. That is not a third splash β€” it is the intro movie's own opening, which the milestone-2 notes describe as "white SQUARE ENIX + cyan glow + red diamonds". A capture-only reading would have recorded a third logo screen that does not exist. **So the first of the five screens now has a reference composite, a framebuffer capture, and its on-screen durations.** **Reach of the two negatives.** Builds 0/1 and 10/11 were looked for in: the whole boot sequence (a 5 s-cadence filmstrip from launch to the title, ~190 s), the title, the main menu, the `EXTRAS` submenu, the `LOAD GAME` slot list, the transition into `MISSION SELECT` (a 40-frame burst), and the attract cycle. They appear in none of them. The obvious remaining candidate is a *long* load β€” a mission launch β€” which is out of this objective's scope; they are most likely a loading/AI-chatter plate. That is a hypothesis, not a result. ## Three things the captures settle beyond the map **The live title is two builds composited.** Build 4 draws the art; build 2 draws `PRESS β’Ά BUTTON` on top, and it **fades in a beat later** β€” a screenshot taken 2.5 s after arriving at the title has the art and no plate, one taken ~1 s later has both. The port must treat the plate as its own timed element. **The attract-loop title carries the plate too.** After ~8–10 s idle the title fades to black, a full-motion video plays for ~85 s, and the title comes back β€” *with* `PRESS β’Ά BUTTON` ([`live-attract-title-press-a-band.png`](captures/title-builds/live-attract-title-press-a-band.png)). This is consistent with, and adds nothing to, the draw-quad comparison in [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md): the plate is not the tell that distinguishes the boot title from the attract title. ### βœ… Refutation attempt (2026-08-30) β€” the two `press-a` captures are DIFFERENT FRAMES The port asked whether `live-title-press-a.png` and `live-attract-title-press-a-band.png` were captured the same way, because it measures **0.301 %** between them and β€” if they were the same frame β€” that number would be a **floor under every full-frame comparison in this corpus**. That is the expensive reading, so it is the one worth attacking. **The attempt to confirm it failed; they are two different moments.** The band is 1279Γ—120 and the full capture 1279Γ—675, so the band was slid down every row of the full frame and scored by mean |Ξ”|. The alignment is unambiguous β€” a sharp minimum, which is the instrument's own control: | y offset | mean abs Ξ” | |---|---| | 519 | 7.887 | | **520** | **4.016** | | 521 | 7.872 | At that best alignment the two disagree on **40.84 %** of the band's pixels (32.56 % by more than 1), mean |Ξ”| **4.02**, max **169**. A crop of the same frame would be zero. So the band is a different instant of a moving screen β€” the movie still running behind the plate β€” and the port's own preferred reading is right. **So 0.301 % is not an instrument floor**, and no full-frame figure in this corpus needs to be discounted by it. ⚠️ Reach: this says the two *captures* differ; it says nothing about whether the two *configurations* differ, because a moving background makes that unanswerable from these two images. A configuration comparison needs two captures of a static screen taken deliberately. **`EXTRAS` is the only main-menu destination inside `GP_TITLE`.** β’Ά on `EXTRAS` opens build 6 β€” measured. The other four destinations leave the archive: β’Ά on `LOAD GAME` opened a `LOAD GAME` slot list, and β’Ά on `MISSION SELECT` inside `EXTRAS` opened a `MISSION SELECT` screen, neither of which is a `GP_TITLE` build. `dat/` carries `GP_SAVE_LOAD.pak`, `GP_TUTORIAL.pak`, `GP_OPTIONS.pak`, `GP_MISSION_SELECT.pak` and `GP_MOVIE_THEATER.pak`; **that those are the archives behind the other four buttons is an inference from the names, not a measurement.** ## How to reproduce ```bash sylpheed-cli screen list "$SYLPHEED_DISC/dat/GP_TITLE.pak" sylpheed-cli screen render --build 6 "$SYLPHEED_DISC/dat/GP_TITLE.pak" /tmp/b6.png tools/re-capture/boot_menu.sh q2 # boots to the main menu, cursor on NEW GAME tools/re-capture/pad.py dpad down 0.35 # x4 -> EXTRAS tools/re-capture/pad.py tap A 0.30 ``` Mind the d-pad hold β€” see [`METHOD.md`](METHOD.md). ## βœ… The title is not a still image β€” it loops, β‰ˆ 2.2 s Recording the title at 10 fps for 22 s shows the screen **never settles**. After the build-in it oscillates by about Β±0.5 in mean luminance, continuously: ``` peaks at 5.6 7.8 10.0 12.7 15.2 17.1 19.2 21.3 s intervals 2.2 2.2 2.7 2.5 1.9 2.1 2.1 mean 2.24 s ``` **measured**, n = 7 intervals, spread 1.9–2.7 s β€” peak-picking a low-amplitude signal is coarse, so read it as **β‰ˆ 2.2 s Β± 0.4**, not a precise period. The mechanism is already decoded: build 4 declares **`ptloop01.rat` / `ptloop02.rat`**, and `loop*.rat` is a **looping sprite animation** rather than a composition ([`INDEX.md`](INDEX.md), UI screen layout row). So the title carries a looping element by construction; what is new is that it runs at β‰ˆ 2.2 s and never stops. ### βœ… The loop records ARE decoded β€” and they are not what I measured `ptloop01.rat` is an **`opt `-linked leaf record**: in build 4 the chunk `opt ` (size `0x0c`) carries the name, immediately followed by a `RATC` blob at **`0xbb5966`** (`ptloop02.rat` the same at `0xbb5a82`). Its pivot fields read `200`/`90`, matching `screen info`'s `pivot (200,90)` β€” the right blob. **The keyframes use the ordinary 40-byte block layout**, starting at `+0x68`: | | `ptloop01` β†’ `pteff03.t32` | `ptloop02` β†’ `pteff03a.t32` | |---|---|---| | kf0 | `t=150` x=**βˆ’639** Ξ±=`ff` | `t=150` x=**1721** Ξ±=`00` | | kf1 | `t=540` x=βˆ’39 Ξ±=`80` | `t=630` x=1111 Ξ±=`80` | | kf2 | `t=600` x=**1521** Ξ±=`ff` | `t=720` x=**βˆ’839** Ξ±=`ff` | | scale | 100 Γ— 600 | 100 Γ— 800 | | span | 150 β†’ 600 = **450 units = 7.5 s** | 150 β†’ 720 = **570 units = 9.5 s** | Y is constant at 270 and X runs off one edge to the other, so these are **horizontal light sweeps** β€” `loop01` left β†’ right, `loop02` right β†’ left. ### πŸ”΄ Two things I wrote last iteration are wrong **1. "A leaf record's keyframes are not in the build's 40-byte layout."** Withdrawn β€” they are, exactly. The scan that "found nothing" demanded **29** strictly-increasing times because I read the word at `+0x004` (`0x001e0000`) as a keyframe count. These records hold **three** keyframes. A filter that hard-codes the expected count rejects the right structure; whatever the `30` is, it is not the number of keyframes here. **2. "The β‰ˆ 2.2 s oscillation is the `ptloop` elements."** Withdrawn β€” I asserted the link because build 4 declares those elements, not because anything showed it. The decoded sweeps run **7.5 s and 9.5 s**. A 22 s capture would show ~3 peaks from a 7.5 s cycle; it showed **8**. So the loops are *not* what the oscillation measured. ### βœ… Identified: it is the `PRESS β’Ά BUTTON` plate pulsing A per-tile amplitude map over the capture (8 Γ— 6 grid, 18 s after build-in) localises the 2.3 s period precisely: | region | sd | dominant lag | |---|---|---| | band x β‰ˆ 318–954, y β‰ˆ 560–672 | **7.65** | **2.3 s** | | wordmark centre | 0.06 | β€” | | bottom-right corner | 0.003 | β€” | That band is the `PRESS β’Ά BUTTON` plate's rest position (`ptbtn00.rat`, rest `(383,550)`, pivot `(256,25)`). It is **build 2**, composited over the title β€” not build 4. Decoding `ptbtn00f.rat`, the plate's highlight variant, gives the pulse directly: | kf | t | alpha | |---|---|---| | 0 | 6 | `0x00` | | 1 | 29 | `0x06` | | 2 | 35 | `0x4a` | | 3 | 50 | `0x50` | | 4 | 58 | `0x50` (hold) | | 5 | 97 | `0x4a` | | 6 | 105 | `0x06` | A glow that fades in to `0x50` and back out β€” exactly a "press start" pulse. There is an **eighth** keyframe: fade `0x00ffffff` at the same position β€” the glow returns to **fully transparent**, so this is a closed cycle, not a one-shot ramp. 🟑 **The cycle length is still not readable β€” and now that is an observation.** The eighth block's time slot contains the four bytes `end `, the record's ASCII terminator: the record simply stops there and the value does not exist. So the corpus's "a group's last block has no time of its own" rule holds here in a second form β€” not the next group's index, but the chunk terminator. Declared span is **β‰₯ 105 units = 1.75 s**; measured **β‰ˆ 2.3 s** (β‰ˆ 138 units), which would need a final step of β‰ˆ 33 units. **That 33 is fitted to the measurement, not read from the file**, and is recorded only so nobody re-derives it as if it were a decode. ❔ **The word at `+0x004` is not a keyframe count.** It reads `0x003c0000` (60) here with 8 keyframes, and `0x001e0000` (30) in the loop records with 3. Whatever it is, it is not the count, and it is not decoded. ### ❔ The declared 4.08 s build-in was not tested That was the intent of this recording and it did not work. The title was reached by **skipping the movie with β’Ά**, which cuts to black and brings the title up on a path that may not be the normal one; and the visible rise (β‰ˆ 2.7 s, from t β‰ˆ 0.8 to 3.5) is a *luminance* curve, which [`screen-transitions.md`](screen-transitions.md) already establishes is **not** the fade quad's ramp. So β‰ˆ 2.7 s neither confirms nor contradicts build 4's declared `16 β†’ 261` (4.08 s); the two are not measuring the same thing. Testing it properly needs the title reached **without** a skip, and a way to separate the quad from the elements β€” neither of which this recording had. ## βœ… For the port: build 4 alone, and groups HOLD rather than loop Both answers the port agent asked for, in one recording. ### The capture β€” the title with no `PRESS β’Ά` plate over it β’· from the main menu returns to the title and the plate fades in **a beat later**, which opens a clean window. Recorded at 20 fps from the press: | | | |---|---| | title art appears | t β‰ˆ 1.10 s | | builds in | 1.10 β†’ 3.70 s (mean 41.8 β†’ 63.2) | | **settled, still no plate** | **3.70 β†’ 5.00 s** | | plate arrives | t β‰ˆ 5.10 s (band jumps 282 β†’ 3 755 bright px) | [`live-title-build4-no-plate.png`](captures/title-builds/live-title-build4-no-plate.png) is t = 4.0 s β€” build 4, settled, unobstructed. That is the reference for the washed-out cyan glow slab the port reports drawing and the game not having. ### βœ… A keyframe group HOLDS at its last keyframe β€” it does not loop This follows from the decoded sweeps plus a measurement, and the two agree: * `ptloop01.rat`'s final keyframe puts `pteff03.t32` at **x = 1521** and `ptloop02.rat`'s puts `pteff03a.t32` at **x = βˆ’839** β€” both **off-screen** on a 1280-wide design. A group that holds therefore parks both sweep sprites out of view and nothing moves after the build-in. * Measured: over 18 s of settled title the centre tiles sit at **sd ≀ 0.01** (per-tile map above). A looping group would recross the screen every **7.5 s** and be unmissable. So the name `loop*.rat` is misleading β€” in this build the records animate **once** during the build-in and then rest off-screen. 🟑 This is about *these* groups on *this* screen; nothing here says no group anywhere loops. ## βœ… The title's settled pose is `rest`, and the "washed-out slab" is a MISSING dim The port agent could not decide whether `rest` or its played-out timeline is the title's arrived pose (they disagree by 142–247/255), and separately reported a *"washed-out cyan glow slab over the title logo that the running game doesn't have"*. The plate-free capture settles both. `sylpheed-cli screen render --build 4 --black`, edge-correlated against [`live-title-build4-no-plate.png`](captures/title-builds/live-title-build4-no-plate.png) β€” a capture of the real screen, so this is an independent oracle and not one renderer checking another: > **0.9163 at shift (0,0)**, and per band 0.92 / 0.78 / 0.93. **Geometry is right**, so `rest` is the arrived pose for the title. ### The slab is the 25 % dim, absent β€” not a glow, present `rest` alone is **uniformly too bright**, and the excess leans cyan: | render | mean (render βˆ’ capture) | R | G | B | |---|---|---|---|---| | `--black` | **+13.14** | +12.35 | +13.58 | +13.48 | | `--black --primitives` | **+0.55** | +1.30 | +0.89 | βˆ’0.54 | Drawing the `.prm` primitives collapses the excess to nothing. The element is **`pteff02.prm`**, the 25 % dim quad (build 4, rest at `t=46`, fade `0x40` = 64 = 25 %) β€” and `--primitives` is **off by default**. So the "washed-out cyan slab" is not something being drawn that shouldn't be. It is the **dim that should be drawn and isn't**: without it every pixel sits ~13 high, and because the title art is blue-dominant the shortfall reads as a cyan wash. ⚠️ Any consumer of `screen render` that omits `--primitives` on this screen gets it. ### 🟑 The residual: right on average, not right per pixel With primitives the *mean* is essentially exact (+0.55) but pixel agreement is slightly **worse** β€” edge-correlation 0.9163 β†’ **0.9066**, and pixels differing by > 20 rise 108 051 β†’ 162 636. So the dim's average contribution is right while its application is not exactly the game's (blend mode or per-region alpha). Not diagnosed. ### πŸ”΄ The real defect: the logo swoosh is drawn white and thick With the dim in place, the residual is **not uniform** β€” it is a dark patch beside a bright one in one band: ``` row1 (y 112-225): -2.0 +3.0 -8.8 -38.6 -17.6 +16.2 +33.8 +24.4 ``` Cropping that band from capture and render ([`title-swoosh-capture-vs-render.png`](captures/title-builds/title-swoosh-capture-vs-render.png)) shows it plainly: the game draws the logo's `Z` **swoosh thin, with a pink/magenta edge**; our render draws it **thick and solid white**. Too bright to its right, too dark where the game's thin stroke actually falls. **This β€” not the missing dim β€” is the port agent's "washed-out slab over the title logo".** The dim explains a *uniform* +13; the slab is this. The elements are `ptlogo_back2.t32` (rest `(71,126)`, pivot `(500,117)` β€” a 1000 Γ— 234 diagonal), its glow `ptlogo_back2eff.t32`, and the five `ptlogo_back2eff1…5` segments at y β‰ˆ 117–194 β€” exactly the band that disagrees. 🟑 **A connection worth chasing, not a diagnosis.** Those five segments are the group whose **paint-order tie-break is the known unsolved residual** ([`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md)): they share key `0x8083`, the game paints them `14,15,18,16,17`, the stable sort paints `14,15,16,17,18`, and the measured cost is "`ptlogo_back2eff5` against `eff3` (22 568 px) and against `eff4` (32 395 px)". **Same screen, same elements.** ❔ But a blend-order swap is a poor explanation for *white instead of pink* β€” that looks like a tint or blend-mode problem, so I would expect a second cause. ### 🟑 A quantified cause for the GEOMETRY: the pivot belongs to the other language The fade and tint fields are **not** the culprit β€” every keyframe of every swoosh element carries `0x??ffffff`, white RGB with only alpha varying, and no tint is anything but `0xffffffff`. Nor is the texture pink: `ptlogo_back2.t32` decodes **blue**-leaning (175, 174, 198) and its glow warm (255, 253, 234). What *is* wrong is the **pivot**. Checking every `GP_TITLE` element whose texture we decode (109 of 178) against the rule *pivot = texture Γ· 2*: | | | |---|---| | exact | 17 | | off by ≀ 1 px (rounding) | 54 | | off by ≀ 8 px | 14 | | **off by > 8 px** | **24** | and the gross ones are **concentrated on the title's logo elements**: ``` a60fcb85 ptlogo_back2.t32 pivot (500,117) texture 1118x262 -> implies (559,131) off 59.0 a60fcb85 ptlogo_back2eff.t32 pivot (507,126) texture 1133x280 -> implies (566,140) off 59.5 a60fcb85 ptlogo2.t32 pivot (449, 46) texture 992x104 -> implies (496, 52) off 47.0 b483e6e6 ptlogo1.t32 pivot (451, 50) texture 822x100 -> implies (411, 50) off 40.0 ``` **Why:** build 4's `ptlogo_back2` pivot `(500,117)` is *exactly* half of the **Japanese** texture (1000 Γ— 234), not its own English one (1118 Γ— 262). The layout record is authored once and shared while the `.t32` sprites are swapped per language β€” the effect [`structures/ui-rat-layout.md`](structures/ui-rat-layout.md) warns about in general, here measured on the screen where the render disagrees with the game. Note it cuts **both ways**: the Japanese build's `ptlogo1` is off by 40 px too. A 59 px offset on a 1118 px sprite is the right order to produce "too thick and extending too far right", which is what the crop shows. ### πŸ”΄ …and that candidate is refuted Checked, as promised. `ui_layout`'s `blit` takes the drawn size from the **texture** (`img.width`/`img.height`), and uses the pivot only for the scale anchor: ```rust let ox = kf.x - (pivot_x as i32 * (sx_pct as i32 - 100)) / 100; ``` At `sx_pct == 100` that term is **zero**. And **every one of the seven swoosh elements is scale `(100,100)` at every keyframe** β€” `ptlogo_back2`, `ptlogo_back2eff` and `ptlogo_back2eff1…5` all report a single scale. So the pivot mismatch, real as it is in the data, **cannot** move or resize the swoosh in our render. ⚠️ It is not harmless everywhere: `ptlogo1`/`ptlogo2` run scales `100 β†’ 101 β†’ 103 β†’ 112 β†’ 150` during the build-in, so there the wrong pivot *does* displace them β€” during the animation, not at rest. ### 🟑 So the swoosh defect is a BLEND problem, by elimination Position and size are the texture's own and are right; fade is white-with-alpha; tint is white; the texture is blue-leaning, not pink. What remains is how the seven overlapping sprites are combined β€” `ptlogo_back2` is only **5.4 % opaque**, its glow 10.3 %, the five `eff` segments 10–23 %, all white or warm. Stacked with plain alpha-over they saturate toward opaque white, which is what we draw and would read as "thicker" beside the game's thin coloured stroke. ❔ Not diagnosed β€” no blend mode has been identified in the data. ### πŸ”΄ Refuted on the way The obvious guess β€” *our dim is applied over the whole frame instead of beneath the UI, where its layer key puts it* β€” is **wrong**. If it were, the logo would render too dark; it reads **+2.36** against a background of **βˆ’0.74**. The compositor honours the paint order here. ## πŸ”΄ The swoosh is not displaced either β€” and the residual is restated Two more candidates eliminated, and the residual is smaller than earlier sections implied. **Not a displacement.** Shifting the render's swoosh band over Β±80 px Γ— Β±8 px and re-correlating peaks **sharply at (0, 0)** β€” 0.7342, falling to 0.22 at Β±24 px and 0.10 at Β±48. The swoosh is where it should be. **Not additive blending.** See [`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md): every measure worsens. **And the residual, restated with the current best render** (`--black --primitives`, `rest()` fixed): | | | |---|---| | whole-frame mean diff | **+0.55** | | swoosh-band mean diff | **+1.83** | | swoosh-band edge-correlation | **0.6971** (vs β‰ˆ 0.92 frame-wide) | ⚠️ Earlier sections quoted band tiles at **+16 … +34**. Those were measured on a render **without** `--primitives`. With the dim drawn the band's *average* is nearly right; what is wrong is its **structure** β€” the tiles run βˆ’38.6 then +33.8 across the band and cancel. So the defect is neither brightness, nor position, nor additive blending: it is a shape difference in one band, and it is **not diagnosed**. Six candidates eliminated: pivot (twice β€” inert at scale 100, and no measured displacement), `fade`, `tint`, texture colour, additive blend. ### πŸ”΄ The capture IS settled β€” my own caveat, tested and withdrawn The previous version of this section worried that the plate-free capture, at t β‰ˆ 4.0 s, might be too early: elements have keyframes to t = 600 (10 s), and "settled" had been judged from mean luminance, which cannot see a thin sprite still moving. The plate sits at y β‰ˆ 550–600, **disjoint** from the swoosh band at y 112–225, so a *late* capture works even with the plate present. Correlating the render's band against the same band at several ages of the screen: | capture | band edge-corr | band mean | |---|---|---| | t = 4.0 s (plate-free) | **0.7342** | 126.63 | | t = 6.0 s | 0.7351 | 126.97 | | t = 12.0 s | 0.7352 | 126.98 | | t = 18.0 s | 0.7353 | 126.98 | | t = 21.5 s | 0.7353 | 126.98 | Identical to within 0.001 over 17.5 s. **The band is settled by t = 4.0 s**, the capture handed to the port agent is sound, and the caveat is withdrawn. It also corroborates that the groups **hold**: nothing crosses that band in 22 s. ## The swoosh β€” SOLVED by draw capture (see below); the elimination trail kept Seven candidates eliminated, none confirmed: | candidate | verdict | |---|---| | element **pivot** (off by 59 px, authored for the other language) | inert β€” `blit` sizes from the texture and applies the pivot only when scale β‰  100; all seven elements are scale `(100,100)` | | **displacement** | none β€” shifting Β±80 Γ— Β±8 px peaks sharply at (0,0), 0.734 β†’ 0.22 at Β±24 px | | **`fade`** | `0x??ffffff` on every keyframe: white RGB, alpha only | | **`tint`** | `0xffffffff` throughout | | **texture colour** | blue-leaning (175,174,198); the glow warm β€” neither pink | | **additive blend** via `T8aD +0x04` bit `0x02` | refuted: every measure worsens | | **capture not settled** | refuted above | The residual is stable and modest: band mean **+1.83**, band edge-correlation **0.6971–0.735** against β‰ˆ 0.92 frame-wide. Real, persistent, and **not located in any field this project can read from the disc**. **Where a next attempt should start, and it is not another field** β€” but check what the tool actually records first, because the obvious phrasing of this is wrong. ⚠️ **The per-draw capture does NOT record blend state.** Reading `command_processor.cc`, each captured draw carries: primitive type, index count, index-buffer address, vertex- and pixel-shader `ucode_data_hash`, the pixel shader's **texture bindings** (base, dimensions, format), and **vertex attribute 0 of binding 0**. There is no `RB_BLENDCONTROL` / `RB_COLORCONTROL` dump. An earlier version of this section claimed the capture "reads the actual blend state"; it does not. So the route splits: * βœ… **Testable today, no code change** β€” whether the game passes a **vertex colour** for those draws. The capture dumps vertex attributes, and a pink vertex colour would explain white-versus-pink directly. * ❔ **Needs a Canary change** β€” the blend mode itself, which means adding an `RB_BLENDCONTROL` dump to the same capture path. Either way it is instrumentation of the running guest, not another field in the file. ## βœ… SOLVED β€” the game draws the swoosh as ROTATED QUADS, which our blit cannot The route named in the previous section was run: `--ui_draw_capture_frames=3`, armed with F10 on the settled title. [`title-draw-capture-vertex-colours.log`](captures/title-builds/title-draw-capture-vertex-colours.log) is the capture, 22 draws over 3 frames. ### πŸ”΄ First, the vertex-colour hypothesis dies Every vertex colour in the entire capture is `FFFFFF` β€” **white RGB**, only the alpha varying: `FFFFFFFF`, `C5FFFFFF`, `C3FFFFFF`, `B8FFFFFF`, `B6FFFFFF`, `31FFFFFF`, `1EFFFFFF`. The game passes no colour. That was the eighth candidate. ### βœ… And the ninth is the answer β€” it is the GEOMETRY Draw 2 submits **two parallelograms, neither axis-aligned**: | quad | corners (NDC) | edge `v0β†’v1` | axis-aligned? | |---|---|---|---| | A | `(0.70,1.58) (1.24,1.02) (0.40,βˆ’1.57) (βˆ’0.14,βˆ’1.02)` | `(0.54, βˆ’0.56)` | **no** | | B | `(βˆ’1.29,1.02) (βˆ’0.85,1.81) (0.75,βˆ’1.02) (0.31,βˆ’1.81)` | `(0.44, 0.79)` | **no** | Both verified parallelograms (opposite edges equal to 0.01), both **rotated** β€” roughly 45Β° and 61Β° β€” and both extending to `y = Β±1.81`, well beyond the screen. That is the diagonal `Z` stroke. **Our compositor cannot draw that.** `ui_layout::blit` walks destination rows and columns of an **axis-aligned rectangle** (`for row in 0..dh { for col in 0..dw`), sampling the source by a straight ratio. It has no rotation. So the swoosh is blitted upright where the game draws it skewed β€” which is exactly the observed signature: **right on average (+1.83), right in position (peak at (0,0)), wrong in structure (edge-corr 0.70)**, dark on one side of the true stroke and bright on the other. ### ❔ And a real gap in the decoded format The keyframe carries `fade`, `scale_x`, `scale_y`, `tint`, `x`, `y`, `time` ([`ui_layout.rs`](../../crates/sylpheed-formats/src/ui_layout.rs)) β€” **no rotation**. So where the game's rotation comes from is **not decoded**: either a field not yet identified, or the element is positioned by code rather than by its keyframes. That is the open question this leaves. ⚠️ **The "pink versus white" reading is now suspect.** It was a visual comparison of two differently-*shaped* renderings. Whether any colour difference survives correct geometry is **untested**, and should be re-checked rather than carried forward as a separate defect. ## 🟑 Three angle fields found β€” and they are NOT the title's rotation The obvious place to look for the rotation was the keyframe block's three words at `+4`, `+8`, `+12`, which `ui_layout.rs` documented as `0`. **They are not zero.** Over **72 287** keyframe blocks disc-wide: | word | non-zero | commonest values (signed) | |---|---|---| | `+4` | **4.81 %** | `180`, `βˆ’180`, `22`, `90` | | `+8` | **4.56 %** | `180`, `90`, `βˆ’180`, `178` | | `+12` | **15.82 %** | `90`, `βˆ’90`, `120`, `βˆ’58` | Values clustering on Β±180, Β±90, 120 read as **degrees**, and three of them suggests rotation about three axes. 🟑 That reading is **not tied to an observed rotation** β€” it is the shape of the numbers, nothing more. The doc comment is corrected either way: "0 on every frame seen" was a sample artefact. ### βœ… They *do* explain this screen β€” the earlier negative was wrong about reach ~~**Every element of `GP_TITLE` build 4 has all three at zero** β€” checked element by element. So the title's rotation comes from outside the keyframe data.~~ **Withdrawn (2026-08-28).** That check walked build 4's **top-level declaration table**. The rotated quads belong to its two **nested leaf records**, and there `+12` reads **30** and **βˆ’45** β€” against a measured **+30.26Β°** and **βˆ’45.28Β°**. The bytes were read correctly; the *region* was too small. See [`structures/ui-keyframe-rotation.md`](structures/ui-keyframe-rotation.md). ⚠️ **One thing I should not have stated flatly:** that the skewed draw *is* the swoosh. It is the only skewed geometry in the capture and the swoosh is the only diagonal element on the screen, so the inference is reasonable β€” but it was not confirmed by matching the draw's texture or screen position to that element, and should be. ## πŸ”΄ The skewed draw is NOT the swoosh β€” my identification was wrong Converting draw 2's quads from NDC to screen space settles it: | quad | screen corners | bbox | |---|---|---| | A | (1088,βˆ’209) (1434,βˆ’7) (896,925) (550,727) | x 550…1434, **y βˆ’209…925** | | B | (βˆ’186,βˆ’7) (96,βˆ’292) (1120,727) (838,1012) | x βˆ’186…1120, **y βˆ’292…1012** | These span the **full screen height and well beyond it**. The swoosh (`ptlogo_back2`, rest `(71,126)`, 1118 Γ— 262) is a **band** at y 126…360. Draw 2 is not it. ## βœ… It is the two `ptloop` sweeps β€” confirmed The bounding box was the wrong measurement; the quads are rotated, so what identifies them is their **edge lengths**: | quad | size | rotation | centre | |---|---|---|---| | A | 400.1 Γ— 1076.3 | **+30.26Β°** | (992.0, 359.1) | | B | 400.2 Γ— 1444.5 | **βˆ’45.28Β°** | (467.2, 360.0) | | quad | element | sprite Γ— declared scale | |---|---|---| | A | `ptloop01.rat` | `pteff03.t32` 399Γ—180 @ 100 %,**600 %** = 399 Γ— **1080** | | B | `ptloop02.rat` | `pteff03a.t32` 399Γ—180 @ 100 %,**800 %** = 399 Γ— **1440** | Five independent agreements, listed in [`structures/ui-keyframe-rotation.md`](structures/ui-keyframe-rotation.md): the count (two quads, two `ptloop` elements), both widths (400 vs 399), **both heights, which are different numbers that both land**, the direction each quad moves between the capture's two frames (matching each record's own sweep direction), and the vertex alphas falling inside the declared ramps. The known-positives in the same capture pass the same test: draw 5 measures 915 Γ— 115 (`ptlogo1.t32` 919Γ—113), draw 7 measures 691 Γ— 18 (`ptcopyright.t32` 694Γ—20) and 512 Γ— 50 (`ptbtn00.t32` 513Γ—50). ### βœ… And "a keyframe group holds" survives The consequence I flagged conditionally last iteration resolves the *other* way. Our renderer parks these two sweeps at their final keyframe (`x = 1521` and `βˆ’839`, both off-screen); the game draws them across the screen. That is not a contradiction β€” the capture caught them **mid-sweep**: the quad centres, x = 992 and x = 467, both fall inside the decoded `t = 150…600` / `150…720` travel, the alphas are mid-ramp, and the sprites move in the decoded direction between frames. The capture is of the **build-in**, not of the settled screen, and the 18 s stillness measurement (sd ≀ 0.01) still says the groups stop. No change to [the holds-not-loops finding](#-a-keyframe-group-holds-at-its-last-keyframe--it-does-not-loop). ### What this costs Five iterations of swoosh work β€” the additive-blend test, the pivot analysis, the vertex-colour capture β€” were built on "the skewed draw is the swoosh", an identification made by *elimination on one screen* and never checked against the draw's own coordinates. The eliminations themselves stand (they were measured against the capture, not against the identification), but the chain of reasoning that pointed at `ptlogo_back2*` did not. --- ## 🟒 Refutation attempt, 2026-08-29 β€” "entries 6/9 are a three-button submenu". It SURVIVED. The port reported `GP_TITLE` entries 6 and 9 as a **three-button** submenu (`ptbtn11/12/13` at x = 532, y = 282 / 362 / 442). This page and HANDOFF call 6/9 `EXTRAS` and say nothing about a button count, and 18 elements looked like too many for three buttons, so the claim was challenged. **The challenge was wrong and the claim stands.** `screen info --all --build 6`: ``` ptframe3.t32 ptframe4.t32 ptbtn11.rat ptbtn12.rat ptbtn13.rat ptmsg2.t32 pteff20.t32 pteff21.t32 pteff22.t32 pteff23.t32 pttitle.t32 ptbase.t32 pteff05.t32 pteff02.prm ptloop01.rat ptloop02.rat pteff10.t32 pteff00.prm ``` Entry 9 is identical. Three `ptbtn*` elements, and the other fifteen are frame, title, background and effect layers. Both statements hold at once: 6/9 **are** `EXTRAS` β€” our composite of entry 6 correlates **+0.944** whole-frame with the committed [`live-extras.png`](captures/title-builds/live-extras.png) β€” **and** `EXTRAS` is a three-button screen. The corpus had never recorded its button count; the port established it, and this page now does too. ⚠️ The challenge was raised from an element *count* without listing the elements, when a one-line `screen info` was available. Recorded because the protocol's adversarial duty is worth nothing if only the successful challenges get written down. ## βœ… Entries 5 and 8 cannot be told apart by layout β€” only by pixels Checked in the same pass, because it bears on which build is which screen state. Entries 5 and 8 have **identical element lists** (`pteff00.prm ptbase pteff05 ptloop01 ptloop02 pteff02.prm ptframe1 ptframe2 pteff10 pteff12 ptbtn01…05 ptmsg`) and identical button placements. The EN/JP difference between the two main-menu bundles lives in the **baked sprite pixels**, not in any layout field. So the 🟑 rule that "the English member of a pair is the one in the first half of the data segment" is not going to be replaced by a layout field for this pair β€” separating 5 from 8 needs either the sprite images or a capture. The same is true of 6 vs 9, whose element lists are also identical.