diff --git a/crates/sylpheed-formats/examples/rat_leaf_placement.rs b/crates/sylpheed-formats/examples/rat_leaf_placement.rs new file mode 100644 index 0000000..e550068 --- /dev/null +++ b/crates/sylpheed-formats/examples/rat_leaf_placement.rs @@ -0,0 +1,64 @@ +//! Does a `.rat` leaf record decode with the same reader as a whole bundle? +//! +//! The port needs the position of `ptbtneff01.t32`, the focus ring, which is +//! declared *inside* the nested `ptbtn0Nf.rat` leaf and is therefore invisible +//! to anything that walks only a bundle's top-level elements. +//! +//! The leaf's first 32 bytes have the same shape as a bundle header -- +//! `"RATC"`, `0x3c` declaration-entry size at `+4`, element count at `+20`, +//! design size at `+24`/`+28` -- so the hypothesis is that `parse_build` reads +//! it unchanged. The control is the BASE record `ptbtn0N.rat`, whose single +//! element's position is already known independently: the parent screen's +//! `screen info` reports `ptbtn01.rat` resting at (542,162). +//! +//! cargo run -p sylpheed-formats --example rat_leaf_placement -- +use sylpheed_formats::{pak, ratc, ui_layout}; + +fn main() { + let path = std::env::args().nth(1).expect("usage: … "); + let ar = pak::PakArchive::open(&path).expect("open pak"); + let entries: Vec<_> = ar.entries().to_vec(); + + for (ei, e) in entries.iter().enumerate() { + let Ok(bytes) = ar.read(e) else { continue }; + let Some(kids) = ratc::parse(&bytes) else { continue }; + // Only the title-family bundles carry ptbtn records. + if !kids.iter().any(|c| c.name == "ptbtn01f.rat") { + continue; + } + println!("=== pak entry {ei}"); + for c in &kids { + if c.kind != "RATC" || !c.name.starts_with("ptbtn") { + continue; + } + let leaf = &bytes[c.offset..c.offset + c.size]; + match ui_layout::parse_build(leaf) { + None => println!(" {:16} …parse_build says no", c.name), + Some(b) => { + println!( + " {:16} {}x{} {} element(s), fallback={}", + c.name, + b.design_w, + b.design_h, + b.elements.len(), + b.from_fallback + ); + for el in &b.elements { + let r = el.rest(); + println!( + " [{}] {:18} pivot ({:4},{:4}) rest ({:5},{:5}) kf {}", + el.index, + el.name, + el.pivot_x, + el.pivot_y, + r.map(|k| k.x).unwrap_or(-1), + r.map(|k| k.y).unwrap_or(-1), + el.keyframes.len() + ); + } + } + } + } + println!(); + } +} diff --git a/docs/re/structures/ui-button-focus-record.md b/docs/re/structures/ui-button-focus-record.md index 10652f6..81d3ef0 100644 --- a/docs/re/structures/ui-button-focus-record.md +++ b/docs/re/structures/ui-button-focus-record.md @@ -98,13 +98,53 @@ 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 the ring is placed — DECODED 2026-08-29, no authoring needed -* **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. +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`](../../../crates/sylpheed-formats/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