re: the rest() fallback -- its example dissolved, the question got bigger
ui-resting-pose.md built its dwell-fallback section on GP_TITLE build 7's ptlogo_eff3.t32, listing keyframes [46, 61, 103, -] -- the STALE PARSER's output. Corrected they are [0, 46, 61, 103], the longest gap moves from 61->103 to 0->46, and BOTH ends of the new longest gap are a=0. The element no longer selects a visible pose under either indexing, and build 7 renders byte-identical under the corrected and legacy readings (0 px differ). MISSION lists this element as the one case a Japanese capture was needed to discriminate; it is not. But losing an example is not closing a question, so: disc-wide census. The fallback fires on 2 305 of 13 991 elements and returns a VISIBLE pose in 1 697 of them -- 74 %. GP_TITLE is 5 fires, 4 visible, and all four are on the SPLASH screens: palogo_sqex_eff and palogo_anima_eff, each [0:a0 15:a255 30:a212 45:a0], a flash peaking at 15 and dead by 45 where the fallback returns t=30 a=212. Independently converged on from the other side: the port, working from the JP capture and knowing nothing of this census, found ptlogo_back2eff1's rest.t at the peak of its own 4-unit sparkle with six staggered across the logo, so --pose=rest fires every sparkle at once -- a frame the game never shows. Consequence recorded as a rule: a render posed at rest is a legitimate common reference for comparing two DECODERS and is not a frame to score against a capture of the game. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
77
crates/sylpheed-formats/examples/rest_fallback_census.rs
Normal file
77
crates/sylpheed-formats/examples/rest_fallback_census.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
//! When the resting-pose DWELL FALLBACK actually runs, does it pick a visible pose?
|
||||
//!
|
||||
//! `ui-resting-pose.md` argues the fallback is structurally unsound — the gap it
|
||||
//! maximises is time spent *interpolating*, so neither endpoint is held. Its one
|
||||
//! worked example, `GP_TITLE` build 7's `ptlogo_eff3.t32`, **no longer
|
||||
//! discriminates**: under the corrected keyframe-record layout the longest gap
|
||||
//! moved from `61→103` to `0→46`, and both ends of that are `a = 0`. The page's
|
||||
//! listing still shows the stale parser's trailing `-`.
|
||||
//!
|
||||
//! Losing the example is not the same as closing the question, so: disc-wide, how
|
||||
//! often does the fallback fire, and when it does, does it return something the
|
||||
//! player would see? An element resting at `a = 0` is harmless whichever end the
|
||||
//! rule lands on.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example rest_fallback_census
|
||||
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||
.expect("dat/")
|
||||
.filter_map(|e| e.ok().map(|e| e.path()))
|
||||
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
|
||||
.collect();
|
||||
paks.sort();
|
||||
|
||||
let (mut elements, mut plateau, mut fallback, mut fb_visible) = (0usize, 0, 0, 0);
|
||||
let mut worst: Vec<(u32, String, String)> = Vec::new();
|
||||
let mut per_pak: std::collections::BTreeMap<String, (usize, usize)> = Default::default();
|
||||
for pak in &paks {
|
||||
let Ok(ar) = PakArchive::open(pak) else { continue };
|
||||
let name = pak.file_name().unwrap().to_string_lossy().to_string();
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let Ok(by) = ar.read(e) else { continue };
|
||||
let Some(b) = ui_layout::parse_build(&by) else { continue };
|
||||
for el in &b.elements {
|
||||
if el.keyframes.len() < 2 { continue }
|
||||
elements += 1;
|
||||
// a plateau is two ADJACENT poses that are equal — the same test
|
||||
// the plateau path makes before the fallback can run
|
||||
let has_plateau = el.keyframes.windows(2).any(|w| {
|
||||
w[0].x == w[1].x && w[0].y == w[1].y
|
||||
&& w[0].scale_x == w[1].scale_x && w[0].scale_y == w[1].scale_y
|
||||
&& w[0].fade == w[1].fade
|
||||
});
|
||||
if has_plateau { plateau += 1; continue }
|
||||
fallback += 1;
|
||||
per_pak.entry(name.clone()).or_default().0 += 1;
|
||||
let Some(r) = el.rest() else { continue };
|
||||
let a = (r.fade >> 24) & 0xff;
|
||||
if a > 0 {
|
||||
fb_visible += 1;
|
||||
per_pak.entry(name.clone()).or_default().1 += 1;
|
||||
worst.push((a, name.clone(), format!("e{i}/{}", el.name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("POPULATION: {elements} elements with >= 2 keyframes, over {} archives", paks.len());
|
||||
println!("COVERAGE: {plateau} have a plateau (fallback never runs)");
|
||||
println!(" {fallback} have NONE -> the dwell fallback decides");
|
||||
println!(" {fb_visible} of those rest at alpha > 0 -- i.e. VISIBLE\n");
|
||||
println!("PER ARCHIVE — fallback fires / of those, rests VISIBLE:");
|
||||
let mut rows: Vec<_> = per_pak.into_iter().collect();
|
||||
rows.sort_by(|a, b| b.1.1.cmp(&a.1.1));
|
||||
for (pak, (fires, vis)) in &rows {
|
||||
println!(" {pak:34} {fires:5} fires {vis:5} visible");
|
||||
}
|
||||
println!();
|
||||
worst.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
for (a, pak, el) in worst.iter().take(6) {
|
||||
println!(" a={a:3} {pak} {el}");
|
||||
}
|
||||
println!("\n--- END OF CENSUS (if this line is missing, the run did not finish) ---");
|
||||
}
|
||||
27
crates/sylpheed-formats/examples/rest_fallback_title.rs
Normal file
27
crates/sylpheed-formats/examples/rest_fallback_title.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Which `GP_TITLE` elements does the resting-pose dwell fallback decide, and does
|
||||
//! it hand back a visible pose? The disc-wide census says 5 fires / 4 visible here.
|
||||
//! cargo run -p sylpheed-formats --example rest_fallback_title
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
use std::path::PathBuf;
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let Ok(by) = ar.read(e) else { continue };
|
||||
let Some(b) = ui_layout::parse_build(&by) else { continue };
|
||||
for el in &b.elements {
|
||||
if el.keyframes.len() < 2 { continue }
|
||||
if el.keyframes.windows(2).any(|w| w[0].x == w[1].x && w[0].y == w[1].y
|
||||
&& w[0].scale_x == w[1].scale_x && w[0].scale_y == w[1].scale_y
|
||||
&& w[0].fade == w[1].fade) { continue }
|
||||
let Some(r) = el.rest() else { continue };
|
||||
let a = (r.fade >> 24) & 0xff;
|
||||
let ks: Vec<String> = el.keyframes.iter()
|
||||
.map(|k| format!("{}:a{}", k.time.map(|v| v.to_string()).unwrap_or("-".into()), (k.fade >> 24) & 0xff))
|
||||
.collect();
|
||||
println!("entry {i:2} {:24} rest a={a:3} t={:?} [{}]{}",
|
||||
el.name, r.time, ks.join(" "),
|
||||
if a > 0 { " <== VISIBLE" } else { "" });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user