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:
@@ -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"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
BIN
docs/re/captures/ui-layout/options-with-its-glow-layer.png
Normal file
BIN
docs/re/captures/ui-layout/options-with-its-glow-layer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
docs/re/captures/ui-layout/options-without-its-glow-layer.png
Normal file
BIN
docs/re/captures/ui-layout/options-without-its-glow-layer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Reference in New Issue
Block a user