From 21456915864f031af91aba98d2198344eeb107b0 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sun, 30 Aug 2026 16:01:13 +0000 Subject: [PATCH] re: verify the sweep leaves' full extent -- port's table confirmed, plus a new fact Attempted to refute sylpheed-port's leaf table by measuring it against the disc. It SURVIVES to the digit: ptloop01 -> pteff03, cycle span 600, x track -639..1521, scale (100, 600); ptloop02 -> pteff03a, span 720, x -839..1721, scale (100, 800). The existing ptloop_leaf_sweep_at.rs samples only t=340..540 -- a window chosen to compare two competing fits -- so it could never have shown the extent. That gap is what let my "ptloop01/02 do not free-run" claim stand: measured over the parent's 200x90 pivot rect, which a leaf travelling -639..1521 is almost never inside. ptloop_leaf_extent.rs sweeps the whole cycle instead. New fact neither of us had: the leaves are IDENTICAL on entries 4, 5 AND 7 -- the title, the main menu and the JP title. Same leaf names, spans, x tracks, scales and parent rest position. So the menu declares exactly the same sweep as the title, and the still-open menu question is about the game's behaviour rather than a different declaration. The quad is 400 px wide at scale_x 100 % -- not widened -- and scale_y 600/800 % makes it 1080/1440 px tall, taller than the screen. A full-height strip crossing the frame and going off both sides, which is why a phase-to-phase diff covers the union of two positions and looks frame-wide. And my own "centre running x~921->1041" was a 30-unit window of a 600-unit cycle whose centre spans -439..1721. A sub-range is not an extent -- the same caution as a pivot not being a bounding box, one level up, and I made both errors within a day. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- .../examples/ptloop_leaf_extent.rs | 51 +++++++++++++++++++ docs/re/data/ptloop-leaf-extent.txt | 47 +++++++++++++++++ docs/re/structures/ui-resting-pose.md | 24 +++++++++ 3 files changed, 122 insertions(+) create mode 100644 crates/sylpheed-formats/examples/ptloop_leaf_extent.rs create mode 100644 docs/re/data/ptloop-leaf-extent.txt diff --git a/crates/sylpheed-formats/examples/ptloop_leaf_extent.rs b/crates/sylpheed-formats/examples/ptloop_leaf_extent.rs new file mode 100644 index 00000000..3c6c0b5f --- /dev/null +++ b/crates/sylpheed-formats/examples/ptloop_leaf_extent.rs @@ -0,0 +1,51 @@ +//! The FULL extent of the two title sweep leaves, across their whole cycle. +//! +//! `ptloop_leaf_sweep_at.rs` samples t=340..540 — a window chosen to compare two +//! competing fits — so it never showed how far the leaves travel. That gap let a +//! claim stand that `ptloop01/02` "do not free-run", measured over the PARENT's +//! 200x90 rect, which is a pivot anchor the leaf spends almost no time inside. +//! `sylpheed-port` reports x tracks of -639..1521 and -839..1721 from their +//! export; this checks that against the disc. +//! +//! cargo run -p sylpheed-formats --example ptloop_leaf_extent +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.pak"); + for entry in [4usize, 5, 7] { + let Ok(by) = ar.read(&ar.entries()[entry]) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + println!("\n######## GP_TITLE entry {entry} ########"); + for parent in ["ptloop01", "ptloop02"] { + let Some(el) = b.elements.iter().find(|e| e.name.starts_with(parent)) else { + println!(" {parent}: not present in this build"); continue }; + let Some(&(off, size)) = b.records.get(&el.name) else { + println!(" {}: no nested record", el.name); continue }; + let span = u32::from_be_bytes(by[off + 8..off + 12].try_into().unwrap()); + let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else { + println!(" {}: leaf will not parse", el.name); continue }; + println!(" {} parent rest ({},{}) nested cycle span {span}", + el.name, el.keyframes.last().map(|k| k.x).unwrap_or(0), + el.keyframes.last().map(|k| k.y).unwrap_or(0)); + for le in &lb.elements { + let (mut lo, mut hi) = (i64::MAX, i64::MIN); + let (mut sxs, mut sys) = (Vec::new(), Vec::new()); + for t in 0..=span { + if let Some(k) = le.pose_at(t) { + lo = lo.min(k.x as i64); hi = hi.max(k.x as i64); + if !sxs.contains(&k.scale_x) { sxs.push(k.scale_x) } + if !sys.contains(&k.scale_y) { sys.push(k.scale_y) } + } + } + let w = (le.pivot_x * 2) as i64; + println!(" leaf {:<12} pivot {}x{} quad w={w} x track {lo} .. {hi} \ + (centre {} .. {}) scale_x {:?} scale_y {:?}", + le.name, le.pivot_x, le.pivot_y, + lo + le.pivot_x as i64, hi + le.pivot_x as i64, sxs, sys); + } + } + } + println!("--- END ---"); +} diff --git a/docs/re/data/ptloop-leaf-extent.txt b/docs/re/data/ptloop-leaf-extent.txt new file mode 100644 index 00000000..0f4e324d --- /dev/null +++ b/docs/re/data/ptloop-leaf-extent.txt @@ -0,0 +1,47 @@ +# The two title sweep leaves, across their WHOLE cycle. 2026-08-30. +# instrument: examples/ptloop_leaf_extent.rs +# +# Why this exists: ptloop_leaf_sweep_at.rs samples t=340..540, a window chosen to +# compare two competing fits, so it never showed how far the leaves travel. That +# gap let a claim stand that ptloop01/02 "do not free-run", measured over the +# PARENT's 200x90 rect -- a pivot anchor the leaf is almost never inside. +# +# sylpheed-port reported x tracks of -639..1521 and -839..1721 from their export. +# Checked against the disc, independently: + +######## GP_TITLE entry 4 ######## + ptloop01.rat parent rest (441,270) nested cycle span 600 + leaf pteff03.t32 pivot 200x90 quad w=400 x track -639 .. 1521 (centre -439 .. 1721) scale_x [100] scale_y [600] + ptloop02.rat parent rest (441,270) nested cycle span 720 + leaf pteff03a.t32 pivot 200x90 quad w=400 x track -839 .. 1721 (centre -639 .. 1921) scale_x [100] scale_y [800] + +######## GP_TITLE entry 5 ######## + ptloop01.rat parent rest (441,270) nested cycle span 600 + leaf pteff03.t32 pivot 200x90 quad w=400 x track -639 .. 1521 (centre -439 .. 1721) scale_x [100] scale_y [600] + ptloop02.rat parent rest (441,270) nested cycle span 720 + leaf pteff03a.t32 pivot 200x90 quad w=400 x track -839 .. 1721 (centre -639 .. 1921) scale_x [100] scale_y [800] + +######## GP_TITLE entry 7 ######## + ptloop01.rat parent rest (441,270) nested cycle span 600 + leaf pteff03.t32 pivot 200x90 quad w=400 x track -639 .. 1521 (centre -439 .. 1721) scale_x [100] scale_y [600] + ptloop02.rat parent rest (441,270) nested cycle span 720 + leaf pteff03a.t32 pivot 200x90 quad w=400 x track -839 .. 1721 (centre -639 .. 1921) scale_x [100] scale_y [800] +--- END --- + +# CONFIRMED to the digit: ptloop01 -> pteff03, span 600, x -639..1521, +# scale (100, 600); ptloop02 -> pteff03a, span 720, x -839..1721, scale (100,800). +# +# 📌 AND A FACT NEITHER OF US HAD: the leaves are IDENTICAL on entries 4, 5 and 7 +# -- the title, the MAIN MENU and the JP title. Same leaf names, same spans, same +# x tracks, same scales, same parent rest position. So the menu carries exactly +# the same sweep as the title, at the declaration level. +# +# The quad is 400 px wide at scale_x 100 % -- not widened -- and scale_y 600/800 % +# makes it 1080 / 1440 px tall, taller than the 720-px screen. A full-height strip +# whose left edge travels -639..1521, i.e. right across the frame and off both +# sides. Two phases of that are hundreds of pixels apart, which is why a +# phase-to-phase diff covers the union of both positions and looks frame-wide. +# +# ⚠️ My own earlier figure, "centre running x~921->1041", is a 30-UNIT WINDOW of a +# 600-unit cycle whose centre spans -439..1721. A sub-range is not an extent -- +# the same caution as a pivot not being a bounding box, one level up. diff --git a/docs/re/structures/ui-resting-pose.md b/docs/re/structures/ui-resting-pose.md index 5d13ccea..14155eb0 100644 --- a/docs/re/structures/ui-resting-pose.md +++ b/docs/re/structures/ui-resting-pose.md @@ -154,6 +154,30 @@ parent is static while the **leaf record animates**, and the two nested records cycle at *different* lengths, **600 and 720**. My "single static keyframe" was a statement about the parent only. +✅ **Their full table also survives an independent check against the disc** +([`data/ptloop-leaf-extent.txt`](../data/ptloop-leaf-extent.txt)), to the digit: + +| | `ptloop01` | `ptloop02` | +|---|---|---| +| leaf | `pteff03` | `pteff03a` | +| cycle span | 600 | 720 | +| x track (left edge) | **−639 … 1521** | **−839 … 1721** | +| scale | (100 %, **600 %**) | (100 %, **800 %**) | + +The quad is 400 px wide and **not** widened; `scale_y` makes it 1080 / 1440 px +tall — a full-height strip taller than the screen, whose left edge travels right +across the frame and off both sides. + +📌 **And a fact neither of us had: these leaves are IDENTICAL on entries 4, 5 and +7** — the title, the **main menu**, and the JP title. Same leaf names, spans, x +tracks, scales and parent rest position. So the menu declares exactly the same +sweep as the title; the open menu question is about the game's *behaviour*, not +about a different declaration. + +⚠️ **My own "centre running x≈921→1041" was a 30-unit window of a 600-unit cycle** +whose centre spans −439…1721. A sub-range is not an extent — the same caution as a +pivot not being a bounding box, one level up, and I made both errors in one day. + ⚠️ The era adjudication is unaffected: its box is x 389..776, and the overlap with the sweep band (x 721..776) shows no between-session differences — the 645 in-box differing pixels all sit at y 99..128.