`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.
52 lines
2.4 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|