//! 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 { "" } ); } } } }