formats: a focused-state record needs the element it is the focused state of

`compose` skips focused-state records by default, so whatever that flag matches
vanishes from every composite. It matched a trailing `f` in the name and nothing
else, which is not the convention — it is a letter.

Measured across the disc: 2458 elements match the suffix and only 54 of them
have the base element they would be the focused version of; all 54 are
`pgmenu_btnNNf.t32`. The other 2404, spread over 864 bundles, are `_eff` glow
layers whose names end in the same letter — `pb_name_eff.t32` alone accounts for
1122 of them, and `palogo_gamearts_eff.t32` is one the draw capture shows the
game painting, before its logo, with its own layer key.

Requiring the pair recovers 587 glows in the composable builds. GP_OPTIONS is
the plainest case: without `pbmwindow_eff.t32` the panel had no window at all,
just two floating brackets (captures/ui-layout/options-with{out,}-its-glow-layer.png).

Two other candidate rules were tried and refuted first. The `opt` link is not a
focus link: 221 elements are opt-targets, only 2 of them suffix-match, and the
targets include `pjnet_bg.rat` and `pv_loading_loop1.rat`. And the suffix alone,
as shown above, is unrelated to anything.
This commit is contained in:
Sylpheed RE agent
2026-08-19 05:57:12 +00:00
parent fb4f035f08
commit 2708abb482
4 changed files with 91 additions and 4 deletions

View File

@@ -113,7 +113,9 @@ pub struct Element {
pub keyframes: Vec<Keyframe>,
/// `opt ` link to another record — the focused state of a button.
pub focus_link: Option<String>,
/// This element is itself a focused-state record.
/// This element is itself a focused-state record: its name is another
/// element's name plus a trailing `f`, and that other element is present.
/// The pairing is required — see `mark_focused_states`.
pub focused: bool,
/// A `loopN` sprite animation rather than a placed element.
pub animated: bool,
@@ -253,13 +255,46 @@ fn parse_decls(bundle: &[u8]) -> Option<Vec<Element>> {
pivot_y: be32(e, 52),
keyframes: Vec::new(),
focus_link: None,
focused: lname.ends_with("f.rat") || lname.ends_with("f.t32"),
focused: false, // needs the whole table — see below
animated: lname.contains("loop"),
});
}
mark_focused_states(&mut elements);
Some(elements)
}
/// Flag the elements that are a **focused variant of another element present in
/// the same build** — `pgmenu_btn00f.t32` next to `pgmenu_btn00.t32`.
///
/// The pairing is not decoration, it is the whole rule. A trailing `f` alone
/// flags **2 458** elements on the disc and only **54** of them have a base to
/// be the focused version of; all 54 are `pgmenu_btnNNf.t32`. The other 2 404,
/// spread over 864 bundles, are `_eff` glow layers whose names merely end in the
/// same letter — `pb_name_eff.t32` (1 122 elements), `pbmwindow_eff.t32`,
/// `pghud_range_eff.t32`, `palogo_gamearts_eff.t32`. `compose` drops focused
/// records by default, so the unpaired rule was deleting a glow layer from
/// nearly every screen that has one.
///
/// The draw capture settles it independently: on the developer-logo splash the
/// three `_eff` glows carry layer key `0xa100` and are **painted**, before their
/// logos (`docs/re/structures/ui-paint-order-key.md`). They are not focus states.
fn mark_focused_states(elements: &mut [Element]) {
let names: std::collections::HashSet<String> = elements
.iter()
.map(|e| e.name.to_ascii_lowercase())
.collect();
for el in elements.iter_mut() {
let l = el.name.to_ascii_lowercase();
if !(l.ends_with("f.rat") || l.ends_with("f.t32")) {
continue;
}
let Some((stem, ext)) = l.rsplit_once('.') else {
continue;
};
el.focused = names.contains(&format!("{}.{}", &stem[..stem.len() - 1], ext));
}
}
/// Read the placement region that follows the declaration table, filling in each
/// element's keyframe group.
fn parse_placements(bundle: &[u8], elements: &mut [Element]) -> Vec<usize> {
@@ -328,7 +363,8 @@ pub fn parse_build(bundle: &[u8]) -> Option<UiBuild> {
// say. Elements without a record (eff*/deli*/msg) are then missing, so
// callers are told via `from_fallback`.
None => {
let els = fallback_elements(bundle, &records);
let mut els = fallback_elements(bundle, &records);
mark_focused_states(&mut els);
let order = (0..els.len()).collect();
(els, true, order)
}
@@ -405,7 +441,7 @@ fn fallback_elements(bundle: &[u8], records: &HashMap<String, (usize, usize)>) -
time: Some(0),
}],
focus_link: opt_link(rec),
focused: lname.ends_with("f.rat"),
focused: false, // set by `mark_focused_states` once the set is known
animated: lname.contains("loop"),
});
}

View File

@@ -515,3 +515,54 @@ fn every_composite_paints_in_layer_key_order() {
to carry the weight the write-up puts on it"
);
}
/// A focused-state record is a **pair**, and the unpaired reading was deleting
/// glow layers from most of the disc.
///
/// `compose` skips focused records by default, so whatever this flag matches
/// disappears from every composite. Matching a trailing `f` alone flagged 2 458
/// elements; only 54 of them — all `pgmenu_btnNNf.t32` — have the base element
/// they would be the focused version of. The rest are `_eff` glows that merely
/// end in the same letter, and the draw capture shows them being painted.
#[test]
fn a_focused_state_always_has_the_element_it_is_the_focused_state_of() {
skip_without_disc!(root);
let (mut total, mut flagged, mut eff) = (0usize, 0usize, 0usize);
for_each_build(&root, |pak, bytes| {
let Some(b) = ui_layout::parse_build(bytes) else {
return;
};
let names: Vec<String> = b
.elements
.iter()
.map(|e| e.name.to_ascii_lowercase())
.collect();
for el in &b.elements {
total += 1;
let l = el.name.to_ascii_lowercase();
if l.ends_with("_eff.t32") {
eff += 1;
assert!(
!el.focused,
"{pak}: {} is flagged as a focused state — it is a glow layer",
el.name
);
}
if !el.focused {
continue;
}
flagged += 1;
let (stem, ext) = l.rsplit_once('.').expect("an extension");
let base = format!("{}.{}", &stem[..stem.len() - 1], ext);
assert!(
names.contains(&base),
"{pak}: {} is flagged as a focused state but {base} is not in \
the build — the pairing requirement has been lost",
el.name
);
}
});
assert!(total > 5000, "only {total} elements — the sweep did not run");
assert!(eff > 100, "only {eff} `_eff` elements, expected the disc's glows");
eprintln!("focused states: {flagged} of {total} elements; {eff} `_eff` glows kept");
}