Files
Sylpheed/docs/re/structures/ui-button-focus-record.md
Sylpheed RE agent 9ca1eb50fd re(ui): answer four of the port's five asks -- splash, fade-out, focus, gamma
1 SPLASH ADDRESSING (was blocking P3). No content predicate exists: design size
fails (every extra composable bundle sampled is 1280x720, like every screen) and
element count fails (fragments run 2..15, the splash halves are 3 and 7). But
GP_TITLE needs none -- `--all` adds exactly four bundles there and all four are
real screens, with the --all index equal to the pak entry index 1:1. And there
are TWO splash screens: 11/14 are the developer logos, 10/13 are the SQUARE ENIX
publisher wordmark, which the port did not have and which the boot shows first.

2 FADE-OUT (was blocking P3). It is (a), and it is bigger than the fade quad.
Every element ends on exactly ONE untimed keyframe, which rules out (b); that
block is where the screen plays out -- quad to a=255, buttons/labels/glows to
a=0, frames hold. (c) is refuted by a null test that discriminates: a black quad
alone holds the button/background brightness ratio constant, and through the
fade it falls 6.50 -> 1.94, 3.4x monotonic.

3 FOCUS (saves P5 rework). Over-vs-instead is unobservable -- the focused sprite
covers the base at 100.0% of base-visible pixels on three pairs once aligned
(true offset (7,7); the centre alignment reads a misleading 78-84%), and
compositing both ways differs by RMSE 1.1 inside the button rect. The real defect
is the focus record's SECOND element: ptbtn0Nf.rat declares ptbtneff01.t32 (a
42x46 glowing ring, focus only) plus the bright label, where the base declares
one sprite. That ring is the marker the port draws nowhere.

5 GAMMA. The capture is not neutral: capture ~ 255*(render/255)^g, g ~ 1.34-1.49,
and the chain says it is a ramp the GAME installed, not a capture artefact. So
RMSE against captures has a floor. Reach stated: the flat patches are all dark
(render ~0-60), so midtones and highlights are unconstrained.

4 ROTATION is a human's call and is recorded in MISSION, not acted on -- the port
rotating while the reference renderer does not would make verify-screen report a
large diff meaning "the port is right". The RE half is answered: rotation is
about the declared pivot, measured against a GPU capture.

The focus record's +20 element-count word is marked 🟡 not  -- read on GP_TITLE's
ten button records only; the disc-wide check is written and still running.
2026-08-29 08:19:23 +00:00

5.2 KiB
Raw Blame History

A focused button is a two-element record, and the second element is the ring

Status: 🟡 DECODED for GP_TITLE, disc-wide check not yet run; the sprite contents and the ring are MEASURED against a live capture. Answers the port's "focus: drawn over the base element, or instead of it?"

⚠️ The element-count field at +20 is read here on GP_TITLE's ten button records only (five buttons × two language bundles). A disc-wide check — "does +20 equal the number of resource names the leaf embeds, for every .rat leaf on the disc?" — is written and was still running when this was committed. Until it lands, treat +20 as a strong local reading, not a decoded field: the observable claim below (the focus record carries a second sprite, ptbtneff01.t32, and the port must draw it) rests on the embedded names and the capture, not on that word.

Short answer: it does not matter, and that is not the bug. The focused sprite covers the base completely, so over-vs-instead is worth at most 12/255 on ~25 pixels. What is missing is the focus record's second element — a glowing ring that only exists when focused.

The record

A button's base and focused variants are both .rat leaves, and they are not the same shape:

ptbtn02.rat   164 B   strings: RATC, ptbtn02.t32,                  opt  ptbtn02f.rat
ptbtn02f.rat  312 B   strings: RATC, ptbtneff01.t32, ptbtn02f.t32, opt  ptbtn02b.rat

The header says so explicitly. A .rat leaf begins:

+0   "RATC"
+16  u32   flags        0x00008110 (base)   0x00008112 (focused)
+20  u32   ELEMENT COUNT      1                    2
+32  first element name

So the focused record declares two elements — ptbtneff01.t32 first, then ptbtn0Nf.t32 — where the base declares one. Both f records of both language bundles read the same way, and the flag word differs only in bit 0x02 alongside the count.

⚠️ The opt link on a .rat leaf is not simply "my focused variant". The chain runs ptbtn01.rat → ptbtn01f.rat → ptbtn02.rat → ptbtn02f.rat → ptbtn02b.rat, i.e. it threads base and focused records together in order. The corpus calls it the focus link and for a base record that reading works; do not generalise it to the f records.

The three sprites

Decoded with sylpheed-cli pak textures, shown over a checkerboard in button-base-focus-ring-sprites.png:

sprite size what it is
ptbtn02.t32 117×43 the dim label, its underline, and a small dot-in-circle at the underline's left end
ptbtn02f.t32 130×56 the same label, bright and glowing — a complete replacement, 13 px larger in each axis
ptbtneff01.t32 42×46 a glowing ring, focus only

The small dot-in-circle is on every button all the time; the large ring is the focus marker. They are different things and sit side by side on the focused row — visible in live-main-menu-options-focused.png against live-main-menu.png.

Over or instead? Measured, and the answer is "unobservable"

Aligning base and focused by normalised cross-correlation of their alpha masks — the true offset is (7,7), not the (6,6) that centring predicts —

pair base-visible px f alpha ≥ base alpha there
ptbtn02 / f (bundle b58a0fe6) 1 444 100.0 %
ptbtn01 / f (bundle b58a0fe6) 1 898 100.0 %
ptbtn02 / f (bundle a715f485) 3 720 100.0 %

⚠️ At the centre alignment it reads 7884 %, and that number is an artefact. A 1 px shift on strokes this thin manufactures a fifth of a sprite's worth of "the focused art is thinner here". Solve the alignment before trusting a coverage figure.

Coverage is not the same as hiding, though, because f is not fully opaque everywhere. Compositing both ways over the menu's own background colour:

pair max channel difference px > 8/255 RMSE over the button rect
ptbtn02 12.5 23 1.09
ptbtn01 12.2 27 1.08
ptbtn02 (JP) 12.2 27 1.05

So the two hypotheses differ by ~1.1 RMSE inside the button rectangle, on a couple of dozen pixels — below the ≈ γ 1.4 tone gap (tone curve) and far below any frame-level RMSE this corpus can resolve. Either choice is defensible; neither is measurable. Replacing is the cheaper one and is what the file's structure suggests, since ptbtn0Nf.t32 is a whole label rather than an overlay.

Not established

  • Where ptbtneff01.t32 is placed. The record declares two elements; the per-element placement inside a .rat leaf was not decoded here. The capture shows the ring left of the label, roughly on the underline's left end. A port should take the offset from the record, and until it is decoded, from the capture.
  • ptbtn02b.t32 — a third variant, b, exists for button 02 only, same size as the base. Not seen on any capture. Not chased.
  • Whether a non-title archive uses the same two-element convention. Checked on GP_TITLE only.