Files
Sylpheed/crates/sylpheed-formats/examples/name_resolution.rs
Sylpheed RE agent 0ed33bcd38 re(ui): a RATC child's name is stated, not inferred -- and it was hiding every menu background
`ratc::parse` named each child by scanning backwards for the last printable run
of bytes before its magic. The format states the name explicitly instead, in an
`opt ` block: `"opt " | BE32 len | name | NUL | 3 bytes | magic` -- the same
block `ui_layout::opt_link` already read for a button's focus link.

The scan agrees with it 17 918 times out of 17 942 and is wrong 24 times, every
one the same failure: the 3 trailing payload bytes are themselves printable and
beat the real name. For `pteff05.t32` those bytes are `38 41 58` = `8AX`, so the
full-resolution background of all five menu screens registered under a name no
element declares, resolved to no sprite, and `compose` dropped it through an
early `continue` that -- unlike the two arms above it -- records nothing. The
screen lost its background and `screen render` still reported "all resolved".

`8AX` was never a name. Docs that treated it as one are corrected here.

Disc-wide, and the control is the 17 918 the scan already got right: the `opt `
reading reproduces every one of them. Effect on the five screens is the
signature of the same art at twice the resolution -- mean brightness unmoved,
high-frequency detail x1.15..x1.30 -- which is what the separately-measured
`ui-8ax-fullres-background` result said the game draws.

Also closes a long-standing dangling reference: `pmbase.t32`, recorded as "on
the disc nowhere", is the `GP_STAGE_CLEAR` child the scan called `8AX`. RATC
sibling references now resolve 10 148 of 10 148.

Verified: 114/114 sylpheed-formats unit tests (including two new ones pinning
the `8AX` case byte for byte and the no-block fallback), and every disc-gated
integration suite in sylpheed-formats/sylpheed-cli.
2026-08-29 07:26:56 +00:00

52 lines
2.4 KiB
Rust

//! Why does an element's sprite fail to resolve? Dump the two name spaces.
//!
//! `parse_build` resolves an element to a sprite by looking its DECLARED name up
//! in (a) the `.rat` record table, then (b) the `T8aD` child table. `pteff05.t32`
//! is in neither -- the `T8aD` it wants is registered as `8AX` -- so it resolves
//! to None and `compose` drops it without recording it as missing. This prints
//! both spaces, so the link between the two can be CHECKED rather than assumed.
//!
//! cargo run -p sylpheed-formats --example name_resolution -- <pak> <entry>
use sylpheed_formats::{pak, ui_layout};
fn main() {
let path = std::env::args().nth(1).expect("usage: name_resolution <pak> [entry]");
let want: Option<usize> = std::env::args().nth(2).and_then(|s| s.parse().ok());
let ar = pak::PakArchive::open(&path).expect("open pak");
let entries: Vec<_> = ar.entries().to_vec();
for (i, e) in entries.iter().enumerate() {
if want.is_some_and(|w| w != i) {
continue;
}
let Ok(bytes) = ar.read(e) else { continue };
let Some(build) = ui_layout::parse_build(&bytes) else { continue };
let unresolved: Vec<&ui_layout::Element> = build
.elements
.iter()
.filter(|el| el.sprite.is_none() && el.kind & 0x10 == 0)
.collect();
let claimed: std::collections::HashSet<&str> =
build.elements.iter().filter_map(|e| e.sprite.as_deref()).collect();
let unclaimed: Vec<&String> =
build.sprites.keys().filter(|k| !claimed.contains(k.as_str())).collect();
if want.is_none() && unresolved.is_empty() && unclaimed.is_empty() {
continue;
}
let un: Vec<&str> = unresolved.iter().map(|e| e.name.as_str()).collect();
let uc: Vec<String> = unclaimed
.iter()
.map(|k| format!("{k}({} B)", build.sprites[*k].1))
.collect();
println!(
"entry {i:3} {:2} elements UNRESOLVED {:?} UNCLAIMED {:?}",
build.elements.len(), un, uc
);
if want.is_some() {
for el in &build.elements {
let mark = if el.sprite.is_none() && el.kind & 0x10 == 0 { " <-- UNRESOLVED" } else { "" };
println!(" [{:2}] kind {:#06x} {:28} -> {:?}{mark}", el.index, el.kind, el.name, el.sprite);
}
}
}
}