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.
53 lines
3.0 KiB
Rust
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());
|
|
}
|
|
}
|