Files
Sylpheed/crates/sylpheed-formats/examples/prm_span_sensitivity.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

146 lines
5.0 KiB
Rust

//! Does `forced_backdrop`'s verdict depend on how the screen's timeline ENDS?
//!
//! The rule quantifies over "every instant the primitive is opaque" and "every
//! element visible then", so both halves depend on where the timeline stops and
//! on what an element does after its own last keyframe. The port asked, and it is
//! the right question: a verdict that flips with the convention is not a decode.
//!
//! Four conventions, all applied to the same disc:
//! A span = max keyframe time over all elements; elements HOLD their last pose
//! (what `forced_backdrop` does, and what the port implements)
//! B span = the primitive's OWN last keyframe time; elements hold
//! C span = the bundle header `+0x08` (the declared length); elements hold
//! D span = max keyframe time; an element is GONE after its own last keyframe
//!
//! D is the one worth the most: it is the assumption the port flagged as "doing
//! real work", and it strictly shrinks the visible set, so it can only turn
//! `forced` into `not forced`.
use sylpheed_formats::{pak, ratc, ui_layout};
fn last_t(el: &ui_layout::Element) -> u32 {
el.keyframes
.iter()
.filter_map(|k| k.time)
.max()
.unwrap_or(0)
}
fn forced(b: &ui_layout::UiBuild, el: &ui_layout::Element, tmax: u32, hold: bool) -> Option<bool> {
if el.sprite.is_some() {
return None;
}
if (el.pivot_x * 2) < b.design_w || (el.pivot_y * 2) < b.design_h {
return None;
}
if tmax == 0 {
return None;
}
let alpha = |e: &ui_layout::Element, t: u32| -> u32 {
if !hold && t > last_t(e) {
return 0;
}
e.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0)
};
let op: Vec<u32> = (0..=tmax).filter(|&t| alpha(el, t) == 255).collect();
if op.is_empty() {
return None;
}
let others: Vec<&ui_layout::Element> =
b.elements.iter().filter(|o| o.index != el.index).collect();
if others.is_empty() {
return None;
}
let below = others
.iter()
.filter(|o| op.iter().any(|&t| alpha(o, t) > 0))
.count();
Some(below == others.len())
}
fn main() {
let root =
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/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 n, mut a_true) = (0usize, 0usize);
let mut flips = [0usize; 3];
let mut examples: Vec<String> = Vec::new();
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for (ei, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let tall = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0);
let hdr = if by.len() >= 12 {
u32::from_be_bytes(by[8..12].try_into().unwrap())
} else {
0
};
for el in &b.elements {
let Some(va) = forced(&b, el, tall, true) else {
continue;
};
n += 1;
if va {
a_true += 1
}
for (k, vb) in [
forced(&b, el, last_t(el), true),
forced(&b, el, hdr, true),
forced(&b, el, tall, false),
]
.into_iter()
.enumerate()
{
if vb != Some(va) {
flips[k] += 1;
if k == 2 && examples.len() < 6 {
examples.push(format!(
"{}:{} {} A={va} D={vb:?}",
p.file_name().unwrap().to_string_lossy(),
ei,
el.name
));
}
}
}
}
}
}
println!("keyless full-screen primitives with an opaque interval: {n}");
println!(" convention A (span = all elements' max, hold) -> forced first: {a_true}\n");
println!(" verdicts that CHANGE under:");
println!(
" B span = the primitive's own last keyframe : {}",
flips[0]
);
println!(
" C span = the header's declared length +0x08 : {}",
flips[1]
);
println!(
" D elements GONE after their last keyframe : {}",
flips[2]
);
for e in &examples {
println!(" {e}")
}
}