Two corrections from one experiment.
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
(title), 8/16 (main menu), 12/18 (EXTRAS), 2/3 and 6/7 (splashes). So the
screen as seen is the HOLD, which is why rest_plateau is the right
primary rule and why "rest = last keyframe" would empty every screen.
That suggested a fix: an element with no hold has no representative pose,
so draw nothing rather than guess an endpoint. Tested through compose's
visible mask and correlated against the live captures:
title +0.9500 -> +0.6839 -0.2661
main menu +0.9460 -> +0.9037 -0.0423
EXTRAS +0.9440 -> +0.9094 -0.0346
Refuted on all three, and the reason invalidates a number I published. An
element with a SINGLE keyframe has no adjacent pair, so the plateau test
marks it plateau-less -- but its one pose is unambiguously its rest.
Suppressing those removes backgrounds and full-screen layers, which is
the title's -0.27.
no plateau (as published) 3 807 (24.57 %)
... single-keyframe 1 502 trivially at rest, not a guess
genuinely ambiguous 2 305 (14.88 %)
So rest() guesses for 2 305 elements, not 3 807 -- the figure I gave the
port overstated the defect by 65%. Corrected in HANDOFF and the page.
METHOD: a predicate over adjacent PAIRS silently misclassifies a
one-element list; and acting on a claim is a better test of it than
re-reading it -- this flaw survived a census, a write-up and a handoff
row, and died the moment the rule was used to change a rendering.
45 lines
2.5 KiB
Rust
45 lines
2.5 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
|
|
let mask: Vec<bool> = build.elements.iter().map(|e| {
|
|
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());
|
|
}
|
|
}
|