Files
Sylpheed/crates/sylpheed-formats/tests/ui_settle_time_disc.rs
sim cc9392bde4 test: one disc resolver, no machine-specific defaults, all three corpora in the container
Finishes #16 in the three places its earlier remedies missed.

`tests/`: the last four local `disc_root()` copies now use `tests/common`, and
with them goes the one real hardcoded fallback — `ui_keyframe_record_disc.rs`
fell back to an absolute path on one machine, which made `unset SYLPHEED_DISC`
a no-op there. Control: with the corpus absent that suite now finishes in 0.00s
instead of 57.55s, so it skips rather than finding a disc of its own.

`examples/`: seventeen examples defaulted to `/disc`, the mount point inside the
CI container. Redundant there — `docker/ci/run` sets `SYLPHEED_DISC=/disc` — and
wrong everywhere else, where a missing corpus turned into a file-not-found
against a path that has never existed on the host. They now name the variable to
set, like the other hundred examples already did.

`docker/ci/run`: mount `$SYLPHEED_RES3D` and `$SYLPHEED_ISO` alongside the disc.
Only the disc was mounted, so an in-container run sat out the res3d and iso
suites while looking like a full one — the defect this issue is about, in the
runner itself.

Measured in the container on this desktop with all three corpora present:
45 suites / 377 passed / 0 failed / 14 ignored, and `sylpheed-corpus-report.txt`
now reports PRESENT for all three rather than for the disc alone.

Refs #16.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:18:59 +02:00

154 lines
5.8 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! A settled screen is one INSTANT, not one hold per element.
//!
//! `Element::rest()` returns an element's last *hold* keyframe, picked for that
//! element alone. For anything that ends the screen settled that is right. For a
//! **transient** it is exactly wrong: a two-frame flash's last hold is the flash
//! *peak*, so `rest()` leaves it burning for the whole screen.
//!
//! `GP_TITLE` build 4 is the case that found this. `ptlogo_back2eff1` … `eff5`
//! are five staggered flashes — `a=0` until t52, `255` for two frames, `0` again
//! two frames later — that sweep left to right across the logo once and are gone
//! by t110. Two elements, `ptlogo_back2eff` (t66238) and `ptlogo_back2`
//! (t80243), then hold for the rest of the screen. `rest()` draws all seven at
//! `a=255` simultaneously, and stacking five extra white glows blows the light
//! arc out to saturation: against the console capture the arc's mean error is
//! 33.22 and 8 581 pixels sit at the clipping level, where the console has 1 459.
//!
//! `UiBuild::settle_time()` recovers the right instant from the disc alone — the
//! midpoint of the longest keyframe-free interval — with no reference to any
//! capture. For this build that is t=198, and posing there takes the arc error to
//! 11.79 and the clipped count to 1 452 against the console's 1 459.
//!
//! Argument, controls and the disc-wide census: `docs/re/structures/ui-settle-time.md`.
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
mod common;
use common::disc_root;
/// The case that found the bug, asserted end to end.
#[test]
fn a_flash_is_transparent_at_the_settle_time_and_opaque_at_rest() {
let Some(root) = disc_root() else {
eprintln!("SYLPHEED_DISC unset — skipping");
return;
};
let arc = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE.pak");
let bytes = arc.read(&arc.entries()[4]).expect("build 4");
let b = ui_layout::parse_build(&bytes).expect("parse");
let (lo, hi) = b.settle_window().expect("a settle window");
let t = b.settle_time().expect("a settle time");
assert!(
hi - lo >= 60,
"title's settle window should be a second or more, got {lo}..{hi}"
);
assert!(
lo < t && t < hi,
"settle time {t} must lie inside {lo}..{hi}"
);
let alpha = |k: &ui_layout::Keyframe| k.fade >> 24;
let mut flashes = 0;
for el in &b.elements {
let Some(name) = el.name.strip_prefix("ptlogo_back2eff") else {
continue;
};
// `ptlogo_back2eff.t32` itself holds; only the numbered ones flash.
if !name.starts_with(|c: char| c.is_ascii_digit()) {
continue;
}
flashes += 1;
let rest = el.rest().expect("a rest pose");
let posed = el.pose_at(t).expect("a posed keyframe");
assert_eq!(
alpha(rest),
255,
"{}: rest() is expected to report the FLASH PEAK — that is the bug",
el.name
);
assert_eq!(
alpha(&posed),
0,
"{} flashes once before t110 and must be gone at the settle time {t}",
el.name
);
}
assert_eq!(
flashes, 5,
"GP_TITLE build 4 has five numbered back2 flashes"
);
// …while the two that genuinely hold are still opaque there.
for want in ["ptlogo_back2eff.t32", "ptlogo_back2.t32"] {
let el = b.elements.iter().find(|e| e.name == want).expect(want);
assert_eq!(
alpha(&el.pose_at(t).expect("posed")),
255,
"{want} holds across the settle time and must stay opaque"
);
}
}
/// The window is a property of the data, so it must be computable disc-wide
/// without panicking, and must be self-consistent wherever it exists.
#[test]
fn settle_windows_are_self_consistent_disc_wide() {
let Some(root) = disc_root() else {
eprintln!("SYLPHEED_DISC unset — skipping");
return;
};
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();
let (mut with, mut wide) = (0usize, 0usize);
for p in &paks {
let Ok(a) = PakArchive::open(p) else { continue };
for e in a.entries() {
let Ok(by) = a.read(e) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some((lo, hi)) = b.settle_window() else {
continue;
};
with += 1;
assert!(lo < hi, "an empty window is not a window: {lo}..{hi}");
let t = b.settle_time().expect("a window implies a time");
assert!((lo..=hi).contains(&t), "settle time {t} outside {lo}..{hi}");
// No element may have a keyframe strictly inside the window — that
// is the whole definition, so it is worth asserting rather than
// trusting.
for el in &b.elements {
for k in &el.keyframes {
if let Some(kt) = k.time {
assert!(
kt <= lo || kt >= hi,
"{}: keyframe t={kt} lies inside the settle window {lo}..{hi}",
el.name
);
}
}
}
if hi - lo >= 30 {
wide += 1;
}
}
}
assert!(
with > 1000,
"expected >1000 bundles with a settle window, got {with}"
);
eprintln!("{with} bundles have a settle window; {wide} are at least 30 units wide");
}