The port asked whether the refined sweep fit t=357.7 was measured against live-title-build4-no-plate.png, because if so one of us is 42 units out. It was not. 357.7 was solved against title-draw-capture-vertex-colours.log, a GPU per-draw capture of the submitted vertex buffer -- four observables at once, two quad centres and two vertex alphas. No framebuffer, no PNG. The gap is not a fitting error either. Posing the leaves directly, t=400 misses the captured quads by +169.0 and -172.2 px. Probe control: it reproduces the page's published t=355 centres, 981 and 478, exactly. Refutation attempted and FAILED: I expected the port's fit to be minimised by the quad leaving the screen -- 'best fit' meaning 'draws least', the same shape as the .tbm control that could not fail. At t=400 quad B is fully on screen and quad A is 319 of 400 px. Their number is fitting something present and it survives. The real reason the two must differ is better than 'different frames'. The sweeps are nested records on a free-running loop and their cycles differ -- 600 and 720, read from the record header +0x08 -- while the top-level clock stops at settle. So two captures of the same settled title share a screen time and not a sweep phase, by construction. Consequence for the port: a sweep position does not date a frame; it gives a phase on a 600- or 720-unit loop. And 357.7 is a joint fit over both leaves while the port's ~400 poses one, so the two are not comparable in kind -- the phases coincide only every LCM 3 600 units. Discriminator handed to the port rather than taken: if its ~400 is pteff03 and the frame is in the first cycle, pteff03a must be at centre 295 in that same frame. The fit is against its renderer, so it owns the check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
92 lines
4.0 KiB
Rust
92 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 {
|
|
""
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|