From 2ea27122ba180fb53482ff4c6f0ea117eebf3863 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Thu, 3 Sep 2026 20:55:12 +0000 Subject: [PATCH] re: enumerate an element's records; focus_link is a misnomer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds examples/element_records.rs, which lists leaf AND focus_link records for an element, plus a disc-wide census. Built because I claimed alpha 80 was undeclared after reading one of ptbtn00's two records -- and focus_link was already parsed, with ui_layout.rs:424 already documenting the focus record. The format was known and I did not consult it. Census: 1467 of 15493 elements (9.5%) across 815 builds carry a second record whose keyframes are invisible to a by-name leaf lookup. Refutes our own parser's description of the field. It is documented as "the focused state of a button", but GP_TITLE has pgloading_loop1 -> loop3 -> loop4, a chain of three loop animations, and ptloop01 -> ptloop02, the two sweeps. Neither is a focused state. Naming defect only -- behaviour is right where it is read -- so not renamed here. 🟑 Notes a better candidate for why the two sweeps share one indices=8 draw: they are linked, not merely co-textured. Testable on the pgloading chain, which needs a loading-screen capture I do not have. Named, not claimed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t --- .../examples/element_records.rs | 73 ++++++++++++++++++ docs/re/element-records-enumerated.md | 75 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 crates/sylpheed-formats/examples/element_records.rs create mode 100644 docs/re/element-records-enumerated.md diff --git a/crates/sylpheed-formats/examples/element_records.rs b/crates/sylpheed-formats/examples/element_records.rs new file mode 100644 index 00000000..fd452b71 --- /dev/null +++ b/crates/sylpheed-formats/examples/element_records.rs @@ -0,0 +1,73 @@ +//! Every record reachable from an element β€” leaf **and** `focus_link` β€” plus a +//! disc-wide census of how many elements have more than one. +//! +//! ⚠️ WHY. I claimed `ptbtn00f`'s peak alpha of 80 was undeclared, having read +//! `ptbtn00.rat` (the leaf, flat 255) and stopped. The pulse is in +//! `ptbtn00f.rat`, the focus record. `focus_link` was already parsed and +//! `ui_layout.rs` already documented it: the format was known and I did not +//! consult it. An absence claim is only as good as its enumeration, so this +//! enumerates rather than asking the reader to remember. +//! +//! cargo run -p sylpheed-formats --example element_records -- GP_TITLE ptbtn00 +//! cargo run -p sylpheed-formats --example element_records -- --census +use sylpheed_formats::{pak::PakArchive, ui_layout}; +use std::path::PathBuf; + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let argv: Vec = std::env::args().skip(1).collect(); + + if argv.iter().any(|a| a == "--census") { + let (mut els, mut linked, mut screens_with) = (0usize, 0usize, 0usize); + let mut paks: Vec<_> = std::fs::read_dir(root.join("dat")).expect("dat") + .filter_map(|e| e.ok()).map(|e| e.path()) + .filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false)).collect(); + paks.sort(); + for p in &paks { + let Ok(ar) = PakArchive::open(p) else { continue }; + for ent in ar.entries() { + let Ok(by) = ar.read(ent) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + let n = b.elements.iter().filter(|e| e.focus_link.is_some()).count(); + els += b.elements.len(); linked += n; + if n > 0 { screens_with += 1 } + } + } + println!("elements disc-wide : {els}"); + println!("with a focus_link record : {linked} ({:.1}%)", 100.0*linked as f64/els as f64); + println!("builds containing at least 1: {screens_with}"); + println!("\nEach of those carries a SECOND record whose keyframes are invisible"); + println!("to anyone who looks up the leaf by name and stops."); + return; + } + + let pak = argv.first().cloned().unwrap_or_else(|| "GP_TITLE".into()); + let want = argv.get(1).cloned(); + let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak"); + for (i, ent) in ar.entries().iter().enumerate() { + let Ok(by) = ar.read(ent) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + for el in &b.elements { + if let Some(w) = &want { if !el.name.starts_with(w.as_str()) { continue } } + if el.focus_link.is_none() && want.is_none() { continue } + println!("entry {i}: {}", el.name); + let stem = el.name.trim_end_matches(".rat"); + for (tag, rec) in [("leaf", format!("{stem}.rat")), + ("focus", el.focus_link.clone().unwrap_or_default())] { + if rec.is_empty() { continue } + match b.records.get(rec.as_str()) { + Some(&(lo, ls)) => { + let bytes = &by[lo..(lo + ls).min(by.len())]; + let loop_u = ui_layout::loop_length_units(bytes); + let kf: Vec = ui_layout::parse_build(bytes).map(|lb| lb.elements.iter() + .map(|e| format!("{} [{} keys, peak a{}]", e.name, e.keyframes.len(), + e.keyframes.iter().map(|k| k.fade >> 24).max().unwrap_or(0))).collect()) + .unwrap_or_default(); + println!(" {tag:<6} {rec:<20} loop {:?} {}", loop_u, kf.join(", ")); + } + None => println!(" {tag:<6} {rec:<20} (no such record)"), + } + } + } + } +} diff --git a/docs/re/element-records-enumerated.md b/docs/re/element-records-enumerated.md new file mode 100644 index 00000000..1b67e556 --- /dev/null +++ b/docs/re/element-records-enumerated.md @@ -0,0 +1,75 @@ +# Enumerating an element's records β€” and `focus_link` is a misnomer + +**Question:** how many records does a UI element actually have, and does my +tooling see them all? + +**What the human looks at:** run +`cargo run -p sylpheed-formats --example element_records -- GP_TITLE ptbtn00`. +Pass = **two** records listed. Fail = one. + +**What this does NOT cover:** F5's code route, the `0x70000` bits. + +**Instrument:** ⟨disc⟩, every `.pak`. + +## Why + +I claimed `ptbtn00f`'s Ξ±80 was undeclared, having read `ptbtn00.rat` β€” the leaf, +flat Ξ±255 β€” and stopped. The pulse is in `ptbtn00f.rat`, reached by `focus_link`. +**`focus_link` was already parsed, and `ui_layout.rs:424` already documented that +`ptbtn0Nf.rat` carries the focus ring.** The format was known; I did not consult +it. So this enumerates rather than asking the next reader to remember: + +``` +entry 2: ptbtn00.rat + leaf ptbtn00.rat loop 120 ptbtn00.t32 [1 keys, peak a255] + focus ptbtn00f.rat loop 120 ptbtn00f.t32 [8 keys, peak a80] +``` + +The fact I missed is the second line. + +## βœ… Census β€” how much of the corpus this exposes + +| | | +|---|---| +| elements disc-wide | **15 493** | +| carrying a second record | **1 467 (9.5 %)** | +| builds containing at least one | **815** | + +Every one of those has keyframes invisible to anyone who looks a leaf up by name +and stops. + +## πŸ”΄ `focus_link` is the wrong name for at least some of its uses + +The parser calls it *"`opt ` link to another record β€” the focused state of a +button"*. On `GP_TITLE` that description fails twice: + +``` +pgloading_loop1 --> pgloading_loop3 --> pgloading_loop4 + (300 units) (120 units) (360 units) +ptloop01 --> ptloop02 + (600 units) (720 units) +``` + +A **chain of three loop animations**, and **the two sweeps**. Neither is a +focused button state. The field links records; "focus" is one thing it is used +for, not what it means. + +Recorded as a naming defect in our own parser, not corrected here β€” the field's +*behaviour* is right everywhere it is read, and renaming it touches every caller. + +## 🟑 A candidate mechanism for the batched draw + +The two sweeps arrive in a **single `indices=8` draw** +([`f6-unit11`](f6-unit11-pteff03a-IS-drawn.md)), which I have been treating as +"they share a texture page". They are also **linked** β€” `ptloop01 β†’ ptloop02`. +That is a better candidate explanation, and it is testable: another linked pair +should batch too. + +**Not tested.** The obvious subject is the `pgloading` chain, and I have no +loading-screen capture. Recorded as 🟑 with the experiment named, not as a +finding. + +## Reach + +The census counts `focus_link` only. If a record can be reached by any *other* +route, this enumeration is still incomplete and the census is a lower bound.