Files
Sylpheed/crates/sylpheed-formats/examples/plateauless_suppression.rs
Sylpheed RE agent a46a922bc8 re(ui): a static composite is only meaningful for a screen that settles
The model's sharpest prediction, tested with its control. The draw log
says that on the developer splash the _eff glows are drawn on frames
94-115 and the logos on 116-211, so at the moment the reference capture
was taken EVERY glow is already finished -- including the two that have
plateaus and which rest_plateau therefore renders visible. Suppressing
them should help on the splashes and hurt where a screen genuinely
settles.

  publisher splash   +0.9604 -> +0.9982   +0.0377
  developer splash   +0.9659 -> +0.9980   +0.0321
  title    (control) +0.9500 -> +0.9480   -0.0020
  main menu(control) +0.9460 -> +0.8544   -0.0916
  EXTRAS   (control) +0.9440 -> +0.8370   -0.1070

Both splashes jump to about 0.998; all three persistent screens get
worse. The control is what makes this a finding rather than a
coincidence: the same edit helps exactly where the model says it should
and hurts exactly where it says it should not.

So rest_plateau is not over-drawing in general -- it over-draws on
TRANSIENT screens. A plateau mid-animation means the element is held at
that point in the timeline, not that it is on screen once the screen has
settled. Where a screen settles, the held pose IS the settled pose and
the rule is measurably right.

And that answers the question left open several iterations ago -- what
"rest" means for a transient element. It does not mean anything: the
splashes never rest. A static composite of them can match a chosen frame,
and about 0.998 is what these captures' frame is worth, but the format
does not answer a question the screen never poses.

For the port: play the timeline for the two splashes, which the settled
keyframe timing now supports, and composite statically for title, main
menu and EXTRAS.

METHOD: an edit that improves one set of cases is only interesting once
you have shown it damages the cases where it should.
2026-08-29 04:37:59 +00:00

53 lines
3.0 KiB
Rust

//! Does DRAWING NOTHING beat guessing, for an element with no held pose?
//!
//! A keyframe group is entry → hold → exit, and the exit ends invisible (on the
//! five port screens the final keyframe is invisible for 21/24, 8/16, 12/18, 2/3
//! and 6/7 elements). So the screen "as seen" is the HOLD — which is why
//! `rest_plateau` is the primary rule. An element with **no** plateau has no
//! hold, and `rest()` currently falls back to guessing an endpoint of a movement.
//!
//! This renders each screen twice — as-is, and with every plateau-less element
//! suppressed via `compose`'s `visible` mask — and correlates both against the
//! live capture. If suppression wins, the fallback should draw nothing.
//! Writes both composites as raw RGBA (`<out>/entryNN_{asis,suppressed}.raw`,
//! 1280x720) so the correlation is done outside — this crate has no image
//! decoder and the comparison is not worth a dependency.
use sylpheed_formats::{pak, ui_layout};
fn main() {
let pak_path = std::env::args().nth(1).expect("usage: <GP_TITLE.pak> <outdir> <entry>...");
let ar = pak::PakArchive::open(&pak_path).expect("open");
let entries: Vec<_> = ar.entries().to_vec();
let outdir = std::env::args().nth(2).expect("outdir");
std::fs::create_dir_all(&outdir).ok();
for spec in std::env::args().skip(3) {
let idx: usize = spec.parse().unwrap();
let bytes = ar.read(&entries[idx]).expect("read");
let Some(build) = ui_layout::parse_build(&bytes) else { continue };
// plateau-less = rest() had to guess: no two adjacent keyframes share a pose
// Default: suppress plateau-less elements. With SUPPRESS_SUBSTR set,
// suppress every element whose NAME contains it instead — used to test
// the entry→hold→exit model's prediction that the splash glows are all
// finished by the moment the logos are up.
let by_name = std::env::var("SUPPRESS_SUBSTR").ok();
let mask: Vec<bool> = build.elements.iter().map(|e| {
if let Some(sub) = &by_name {
return !e.name.to_lowercase().contains(sub.as_str());
}
let k = &e.keyframes;
(0..k.len().saturating_sub(1)).any(|i| {
k[i].fade == k[i+1].fade && k[i].scale_x == k[i+1].scale_x
&& k[i].scale_y == k[i+1].scale_y && k[i].x == k[i+1].x && k[i].y == k[i+1].y
})
}).collect();
let suppressed = mask.iter().filter(|m| !**m).count();
let opts = ui_layout::ComposeOptions::default();
let a = ui_layout::compose(&build, &bytes, opts, None);
let b = ui_layout::compose(&build, &bytes, opts, Some(&mask));
std::fs::write(format!("{outdir}/entry{idx:02}_asis.raw"), &a.rgba).unwrap();
std::fs::write(format!("{outdir}/entry{idx:02}_suppressed.raw"), &b.rgba).unwrap();
println!("entry {idx:2} {}x{} elements {:2} plateau-less suppressed {suppressed}",
a.width, a.height, build.elements.len());
}
}