formats: check the derived paint order against the screens already verified

The layer-key order was adopted from two measured screens and then applied to
every build on the disc, so it owed a regression check against the screens the
corpus had already validated against the running game.

Rendered the tutorial PAUSE menu and the title main menu both ways and diffed:
3.8 % and 1.1 % of pixels differ, max delta 45/255 and 34/255, and the two
renders are indistinguishable in layout — the change is confined to blends where
translucent sprites overlap. No regression, but which order is more faithful on
those two screens is unsettled and recorded as such.

Adds a corpus-wide test asserting every composite's draw list is strictly
increasing in (layer key, declaration index), streaming one pak at a time so it
does not OOM alongside the other whole-disc tests. It reports the rule's reach:
341 of 965 builds are reordered, and it fails if that share collapses.
This commit is contained in:
Sylpheed RE agent
2026-08-19 05:43:08 +00:00
parent d9ae42dd55
commit deff5a4d6d
3 changed files with 143 additions and 4 deletions

View File

@@ -71,6 +71,31 @@ fn builds(root: &Path) -> Vec<(String, Vec<u8>)> {
out
}
/// Every parseable screen build, handed over **one pak at a time**.
///
/// `builds` holds the whole disc's bundles in memory at once, and four tests in
/// this file want the whole corpus; run together under the default test harness
/// that is enough to get the process OOM-killed. This streams instead.
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
for p in &paks {
let name = p.file_name().unwrap().to_string_lossy().to_string();
let Ok(arc) = PakArchive::open(p) else { continue };
for e in arc.entries() {
let Ok(bytes) = arc.read(e) else { continue };
if ui_layout::is_build(&bytes) {
f(&name, &bytes);
}
}
}
}
/// The builds of one pak, in the order `sylpheed-cli screen list` numbers them.
fn pak_builds(root: &Path, pak: &str) -> Vec<Vec<u8>> {
let arc = PakArchive::open(root.join("dat").join(pak)).expect("open pak");
@@ -427,3 +452,66 @@ fn the_derived_order_matches_the_measured_ones_up_to_ties() {
"expected both measured builds; seen = {seen:?} over {checked} matches"
);
}
/// The layer-key order is what every composite on the disc actually paints in,
/// and it is not a no-op dressed up as a discovery.
///
/// Two things are checked corpus-wide, because the derived order was adopted on
/// the strength of **two** measured screens and then applied to all of them:
///
/// * every composite's draw list is non-decreasing in the layer key, so the
/// rule really reaches the whole corpus and a future accidental revert to
/// declaration order fails here rather than silently;
/// * the derived order reorders a substantial share of the disc's builds. If it
/// were a near-no-op the two measured screens would be the only evidence
/// there is, and the rule would deserve much less credit than it has.
#[test]
fn every_composite_paints_in_layer_key_order() {
skip_without_disc!(root);
let (mut total, mut checked, mut reordered) = (0usize, 0usize, 0usize);
for_each_build(&root, |pak, bytes| {
total += 1;
let Some(b) = ui_layout::parse_build(bytes) else {
return;
};
if b.from_fallback {
return;
}
let key = |i: usize| {
(
ui_layout::sprite_layer_key(&b, bytes, &b.elements[i]).unwrap_or(u32::MAX),
i,
)
};
let mut want: Vec<usize> = (0..b.elements.len()).collect();
want.sort_by_key(|&i| key(i));
if want != (0..b.elements.len()).collect::<Vec<_>>() {
reordered += 1;
}
let c = ui_layout::compose(&b, bytes, ComposeOptions::default(), None);
// The two screens read off the running game keep their measured order,
// which agrees with the derived one only up to ties — skip those.
if c.drawn.len() < 2 || b.elements.len() == 24 || b.elements.len() == 7 {
return;
}
checked += 1;
for w in c.drawn.windows(2) {
assert!(
key(w[0]) < key(w[1]),
"{pak}: painted element {} (key {:#x}) before {} (key {:#x}) — the \
composite is no longer in layer-key order",
w[0],
key(w[0]).0,
w[1],
key(w[1]).0
);
}
});
assert!(checked > 100, "only {checked} builds composed 2+ elements");
eprintln!("layer-key order: {reordered}/{total} builds reordered");
assert!(
reordered * 4 > total,
"the derived order reorders only {reordered} of {total} builds — too few \
to carry the weight the write-up puts on it"
);
}