Closing the residual I left explicitly unidentified last iteration, and correcting my own claim twice over in the process. The cheap test first: is the oscillation global? No -- the bottom-right corner is flat at sd 0.003 and uncorrelated with the whole frame. So a per-tile amplitude map over an 8x6 grid, which localises it hard: sd 7.65 in the band x 318..954, y 560..672 at lag 2.3 s, against 0.06 on the wordmark. That band is the PRESS (A) BUTTON plate's rest position. Decoding ptbtn00f.rat, the plate's highlight variant, gives the pulse itself: alpha 0x00 -> 0x06 -> 0x4a -> 0x50, held, then back down through 0x4a to 0x06 across t=6..105. A glow that fades in and out, which is exactly what a press-start prompt does. So "the title screen loops at 2.2 s" was wrong in both halves. The title ART is near-static apart from the two decoded sweeps at 7.5 s and 9.5 s; what pulses is the PLATE, and the plate is build 2, not build 4. REFUTED carries it under my own name. Still amber, and said so rather than rounded off: the cycle LENGTH is not readable. The keyframe group's last block has no time -- that slot belongs to the next group -- so the declared span is at least 105 units, 1.75 s, against a measured 2.3 s. Consistent with a final block extending the tail. Not confirmed.
304 lines
15 KiB
Markdown
304 lines
15 KiB
Markdown
# 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; ❔ open for the two `DELTASABER` plates.
|
||
|
||
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 | a `DELTASABER / SYLPHEED A.I.` plate low-left on black, no background | ❔ **not observed running.** Never seen in the boot path, the main menu, `EXTRAS` or `MISSION SELECT` |
|
||
| **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 same `DELTASABER` plate as build 0, over a dark circuit-line background | ❔ **not observed running** |
|
||
|
||
## ✅ 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.
|
||
|
||
**`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.
|
||
|
||
🟡 **The cycle length is still not readable.** The group's last block has no time
|
||
(that slot belongs to the next group), so the declared span is **≥ 105 units =
|
||
1.75 s** against the measured **≈ 2.3 s** (138 units). Consistent with a final
|
||
block extending the tail; not confirmed.
|
||
|
||
### ❔ 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.
|