re(ui): the focus ring SPINS, the game draws it, and the leaf owns the f record
Three things, all from parsing ptbtn0Nf.rat as a build. 1. THE RING SPINS. Its two keyframes differ in exactly one field: rotation_deg ramps 0 -> 360 with position, scale, alpha and tint all constant. A spin in place, the same shape as the GP_BUNK example already recorded. 2. THE ORACLE CONFIRMS THE GAME RENDERS IT. In the OPTIONS-focused capture the ring's bright head sits in a completely different angular position from the sprite's own -- caught mid-spin. This is a SECOND independent confirmation that rotation_deg is drawn, now on a different screen and a different element from the ptloop sweeps, and it raises rotation's priority: it is not a title-only concern that sits off-screen at rest, it is the main menu's focus marker. NO ANGLE IS QUOTED. A brightest-region centroid says ~250 deg, but the control refuses that precision -- rotating the sprite by a known 30/90/180/270 and re-measuring gives errors up to 19.8 deg. What survives the error bar is that a <=20 deg error cannot manufacture a ~250 deg displacement. 3. WHICH PLACEMENT WINS -- correcting this page's own earlier caveat, which said to use the leaf only for elements the parent does not declare. Right for a BASE record, wrong for an f record: the parent declares NO element for ptbtn0Nf.rat at all (zero of build 5's 16), so the f record's placement comes from its leaf for BOTH elements, label included. The label's (-7,-7) is load-bearing -- the f sprite is 13px larger per axis and -7 keeps them concentric (535+96/2 = 583 vs 542+83/2 = 583.5). Corroborated against the oracle: the focused-minus-unfocused region is x 505..703, and the leaf predicts a right edge near 707 where the parent reading predicts 714. Also exposes UiBuild::records (name -> (offset, size) of a nested .rat leaf). Nested records were parsed into a PRIVATE map, so a consumer holding a UiBuild could not locate a leaf's bytes at all -- which is exactly what blocked the port from reaching the ring.
This commit is contained in:
@@ -55,6 +55,13 @@ fn main() {
|
||||
r.map(|k| k.y).unwrap_or(-1),
|
||||
el.keyframes.len()
|
||||
);
|
||||
for (i, k) in el.keyframes.iter().enumerate() {
|
||||
println!(
|
||||
" kf{i} t={:?} pos=({},{}) scale={}%,{}% a={} rot={} tint={:#010x}",
|
||||
k.time, k.x, k.y, k.scale_x, k.scale_y,
|
||||
k.fade >> 24, k.rotation_deg, k.tint
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,6 +322,20 @@ pub struct UiBuild {
|
||||
pub elements: Vec<Element>,
|
||||
/// Sprite name → (offset, size) of its `T8aD` child within the bundle.
|
||||
pub sprites: HashMap<String, (usize, usize)>,
|
||||
/// Record name → (offset, size) of its nested `.rat` **leaf** within the
|
||||
/// bundle, e.g. `ptbtn01f.rat`.
|
||||
///
|
||||
/// Exposed because a leaf is where a focused button's extra elements live —
|
||||
/// `ptbtn0Nf.rat` declares the focus ring `ptbtneff01.t32` **and** the bright
|
||||
/// label, and the parent bundle declares no element for the `f` record at
|
||||
/// all. A consumer that walks only top-level elements cannot see either.
|
||||
///
|
||||
/// A leaf is itself readable by [`parse_build`]: its first 32 bytes have the
|
||||
/// same shape as a bundle header, so
|
||||
/// `parse_build(&bundle[off..off + size])` returns its elements with
|
||||
/// absolute design-space placements. See
|
||||
/// `docs/re/structures/ui-button-focus-record.md`.
|
||||
pub records: HashMap<String, (usize, usize)>,
|
||||
/// A guessed context from the sprite naming (e.g. `"tutorial"`), if any.
|
||||
pub context_hint: Option<String>,
|
||||
/// True when the declaration table was unusable and the build was recovered
|
||||
@@ -599,6 +613,7 @@ pub fn parse_build(bundle: &[u8]) -> Option<UiBuild> {
|
||||
design_h,
|
||||
elements,
|
||||
sprites,
|
||||
records,
|
||||
context_hint,
|
||||
from_fallback,
|
||||
placement_order,
|
||||
|
||||
Reference in New Issue
Block a user