This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
4.0 KiB
Rust
95 lines
4.0 KiB
Rust
//! Where are the title's two light-sweep quads at a given time — and is a
|
|
//! best-fit against a framebuffer PNG even measuring their position?
|
|
//!
|
|
//! `ui-leaf-vs-parent-alpha.md` solves the sweep instant as **t = 357.7** from a
|
|
//! GPU per-draw capture: quad centre x measured off the submitted vertex buffer,
|
|
//! which is a position measurement at 4 px/unit. The port agent separately
|
|
//! best-fits the same leaf against `live-title-build4-no-plate.png` and gets
|
|
//! **~400 units**, and asked whether the two used the same capture.
|
|
//!
|
|
//! Before comparing the numbers, check whether the second method can see what it
|
|
//! claims to measure. A fit that is minimised by the quad being OFF-SCREEN is
|
|
//! minimised by absence, and would return "best" at whatever time draws least —
|
|
//! the same shape as the `.tbm` control that could not fail.
|
|
//!
|
|
//! So: print the leaves' own x, alpha, and on-screen overlap across the window.
|
|
//!
|
|
//! cargo run -p sylpheed-formats --example ptloop_leaf_sweep_at
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
|
|
|
const SCREEN_W: i64 = 1280;
|
|
|
|
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.pak");
|
|
let by = ar.read(&ar.entries()[4]).expect("entry 4");
|
|
let b = ui_layout::parse_build(&by).expect("parse");
|
|
|
|
for parent in ["ptloop01", "ptloop02"] {
|
|
let Some(el) = b.elements.iter().find(|e| e.name.starts_with(parent)) else {
|
|
eprintln!("no element {parent}");
|
|
continue;
|
|
};
|
|
let Some(&(off, size)) = b.records.get(&el.name) else {
|
|
eprintln!("{}: no nested record", el.name);
|
|
continue;
|
|
};
|
|
// `ui-record-loop-length.md`: a nested record's header `+0x08` is its
|
|
// CYCLE LENGTH, and its keyframes need not fill it. This is why two
|
|
// captures of the same settled title do not share a sweep phase.
|
|
let loop_len = u32::from_be_bytes(by[off + 8..off + 12].try_into().unwrap());
|
|
println!(
|
|
"\n-- {} nested record: loop length (+0x08) = {loop_len}",
|
|
el.name
|
|
);
|
|
let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else {
|
|
eprintln!("{}: leaf will not parse", el.name);
|
|
continue;
|
|
};
|
|
for le in &lb.elements {
|
|
let w = (le.pivot_x * 2) as i64;
|
|
println!(
|
|
"\n== {} -> leaf {} (sprite {:?}, pivot {}x{}, {} keyframes, last t={:?})",
|
|
el.name,
|
|
le.name,
|
|
le.sprite,
|
|
le.pivot_x,
|
|
le.pivot_y,
|
|
le.keyframes.len(),
|
|
le.keyframes.last().map(|k| k.time)
|
|
);
|
|
// ⚠️ A keyframe's `x` is the quad's LEFT edge, not its centre. The
|
|
// draw-capture fit is quoted in CENTRES, so compare `centre`, which is
|
|
// `x + pivot_x`. Printing `x` under a "centre" heading is how a
|
|
// 200-px offset gets into a comparison unnoticed.
|
|
println!(" t | x | centre | a | on-screen px of a {w}px-wide quad");
|
|
for t in [
|
|
340u32, 350, 355, 357, 358, 360, 370, 380, 390, 395, 400, 405, 410, 420, 440, 480,
|
|
540,
|
|
] {
|
|
let Some(k) = le.pose_at(t) else {
|
|
println!(" {t:>4} | (no pose)");
|
|
continue;
|
|
};
|
|
let x = k.x as i64;
|
|
let a = k.fade >> 24;
|
|
let l = x;
|
|
let r = l + w;
|
|
let vis = (r.min(SCREEN_W) - l.max(0)).max(0);
|
|
println!(
|
|
" {t:>4} | {x:>4} | {:>6} | {a:>3} | {vis:>5} px {}",
|
|
x + le.pivot_x as i64,
|
|
if vis == 0 {
|
|
"*** ENTIRELY OFF SCREEN ***"
|
|
} else {
|
|
""
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|