Files
Sylpheed/crates/sylpheed-formats/examples/element_records.rs
sylph-decoder baf285d366 re: enumerate an element's records; focus_link is a misnomer
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-03 20:55:12 +00:00

74 lines
3.9 KiB
Rust

//! 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<String> = 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<String> = 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)"),
}
}
}
}
}