This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/ui-button-focus-record.md
Sylpheed RE agent b21c8e4118 re(ui): the focus ring's position is decoded -- a .rat leaf parses as a build
The port needed ptbtneff01.t32's placement and was about to author it from an
eyeballed PNG measurement. It does not have to: a `.rat` leaf needs no new
reader. Its first 32 bytes have a bundle header's shape -- "RATC", 0x3c
declaration-entry size at +4, element count at +20, design 1280x720 at +24/+28 --
so ui_layout::parse_build reads it unchanged.

The control is the base record, whose position is known independently: the parent
screen reports ptbtn01.rat resting at (542,162), and parsing the leaf alone
returns ptbtn01.t32 at (542,162). It reproduces all five buttons.

Positions are absolute design-space top-left. The ring rests at (500, 156/236/
316/396/476) for buttons 1-5 -- a uniform (-42,-6) from each button's own rest,
identical in the Japanese bundle. The bright label is a uniform (-7,-7).

Two things recorded rather than smoothed over: a leaf's placement DUPLICATES the
parent's rather than being relative to it, and the two copies are not always
byte-equal (ptbtn04's parent says y=401, its leaf says 402) -- the parent is what
compose honours, so the leaf is the source only for elements the parent does not
declare, which is exactly the ring. And `screen render --focus` is blind to the
ring for the same reason the port's exporter was: el.focused is name-based on
top-level elements and neither walks into the leaf.
2026-08-29 08:36:53 +00:00

7.5 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.

Where the ring is placed — DECODED 2026-08-29, no authoring needed

This page previously said the per-element placement inside a .rat leaf was not decoded and that a consumer should eyeball it off a capture. That was wrong by omission: a leaf needs no new reader. Its first 32 bytes have the same shape as a bundle header — "RATC", 0x3c declaration-entry size at +4, element count at +20, design size 1280x720 at +24/+28 — so ui_layout::parse_build reads it unchanged.

The control is the base record, whose position is known independently: the parent screen reports ptbtn01.rat resting at (542,162), and parsing the leaf on its own returns ptbtn01.t32 at (542,162). It reproduces all five.

Positions are absolute design-space top-left, not offsets (examples/rat_leaf_placement.rs):

button base ring ptbtneff01.t32 Δ label ptbtn0Nf.t32 Δ
ptbtn01 (542,162) (500,156) (42,6) (535,155) (7,7)
ptbtn02 (542,242) (500,236) (42,6) (535,235) (7,7)
ptbtn03 (542,322) (500,316) (42,6) (535,315) (7,7)
ptbtn04 (542,402) (500,396) (42,6) (535,395) (7,7)
ptbtn05 (542,482) (500,476) (42,6) (535,475) (7,7)

The offset is uniform: (42,6) for the ring and (7,7) for the label on every button, and identical in the Japanese bundle (pak entry 8).

⚠️ One 1-unit disagreement, and it is real. ptbtn04's parent element rests at y 401 while its own leaf says y 402 (and in the JP bundle the leaf says 401 against a parent 401). So a leaf's placement duplicates the parent's rather than being relative to it, and the two copies are not always byte-equal. The parent's is what compose honours; treat the leaf's as the source only for elements the parent does not declare — which is exactly the ring's case.

🟡 The ring carries 2 keyframes where the label carries 1, so it animates. What it does between them is not decoded here.

⚠️ screen render --focus is blind to the ring, and so was this page

el.focused is name-based on top-level elements, and a screen's buttons are .rat records whose focused twin is not itself a top-level element — so rendering build 5 with and without --focus produces an identical image. The reference renderer has the same blind spot the port reported, for the same reason: neither walks into the leaf. Fixing it is a renderer change, not a format question; the format is decoded above.

Not established

  • 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.