diff --git a/crates/sylpheed-formats/examples/ptloop_parent_keyframes.rs b/crates/sylpheed-formats/examples/ptloop_parent_keyframes.rs new file mode 100644 index 00000000..f2e80466 --- /dev/null +++ b/crates/sylpheed-formats/examples/ptloop_parent_keyframes.rs @@ -0,0 +1,32 @@ +//! The PARENT elements that host the sweep leaves — `ptloop01`/`ptloop02` as +//! they are declared in the title build itself, not in their nested records. +//! +//! The leaves' own alpha is non-zero at their t=0 (`pteff03` declares 255), yet +//! the capture shows the sweep fading in from ~8. So the gate is the parent's +//! alpha, and this prints it. +//! +//! cargo run -p sylpheed-formats --example ptloop_parent_keyframes -- GP_TITLE 5 +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 argv: Vec = std::env::args().skip(1).collect(); + let pak = argv.iter().find(|a| a.parse::().is_err()) + .cloned().unwrap_or_else(|| "GP_TITLE".to_string()); + let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak"); + let builds: Vec = argv.iter().filter_map(|a| a.parse().ok()).collect(); + for e in if builds.is_empty() { vec![5usize] } else { builds } { + let Ok(by) = ar.read(&ar.entries()[e]) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + println!("=== {pak} entry {e}: {} elements ===", b.elements.len()); + for el in &b.elements { + for (i, k) in el.keyframes.iter().enumerate() { + println!(" {:<20} kf{i:<2} t={:<5} x={:<6} y={:<6} sx={:<4} sy={:<4} fade={:08X} (alpha {:3})", + el.name, + k.time.map(|t| t.to_string()).unwrap_or_else(|| "-".into()), + k.x, k.y, k.scale_x, k.scale_y, k.fade, k.fade >> 24); + } + } + } +} diff --git a/docs/re/f6-unit10-parent-alpha-gates-the-sweep.md b/docs/re/f6-unit10-parent-alpha-gates-the-sweep.md new file mode 100644 index 00000000..a6f6ff7c --- /dev/null +++ b/docs/re/f6-unit10-parent-alpha-gates-the-sweep.md @@ -0,0 +1,80 @@ +# F6 unit 10 — what makes the sweep start, and when + +**Question:** why is the sweep hidden for the first part of the title, and what +turns it on? + +**What the human looks at:** the sweep should be absent at the very start, fade +up once the title art is established, and then keep looping. Pass = it fades in +partway through the build-in. Fail = present from frame one, or popping in. + +**What this does NOT cover:** F5, the plate's identity, and the loop period. + +**Instruments:** ⟨disc⟩ for the declarations, ⟨capture⟩ for the two runs. + +## ✅ Decoded — the gate is the PARENT's alpha, and it is declared + +`GP_TITLE` **build 4** (and 7) declare both hosts with a five-key ramp: + +``` +ptloop01.rat / ptloop02.rat t=0 α0 · t=70 α0 · t=100 α255 · t=238 α255 · t=250 α0 +``` + +**Alpha 0 until `t=70`, up to full by `t=100`, and `238…250` is an EXIT ramp.** +That answers the brief's open question directly: for *these* elements 238…250 +starts nothing, it ends them. + +⚠️ **Build matters.** Builds **5 and 6** declare the same two records as a single +keyframe at flat α255 — no ramp at all. So "what does `ptloop01` do" has no +answer until the build is named, and the one the human is watching is 4/7. + +## ✅ Measured — the parent's alpha is multiplied into the leaf's + +The leaf `pteff03` declares α **255 falling to 128** across its own `t=0…150`. +The capture shows the sweep **rising 8→255** in that same window, so the leaf +alone predicts the wrong direction — the control could have flattered the +hypothesis and did not. + +Decomposing `observed = parent × leaf / 255` using the leaf's declared curve, the +implied parent **ramps up and then pins at 255.0 and holds**: 254.0–256.9 (f6) +and 253.9–254.9 (f6b) across hundreds of frames, while the observed alpha swings +242 → 132 → 145. Over that whole swing the leaf's declared curve predicts the +drawn alpha **to within one alpha level**. + +A flat 255.0 residual across a large excursion is the part that could have +failed. It settles the standing 🟡 on whether parent alpha is multiplied in: +**it is.** + +## ✅ Leaf runs at half the title's clock — and this restores a number I withdrew + +Using the parent's declared 30-unit ramp (`t=70…100`) as an in-capture title +clock, and the leaf's declared 600-unit loop as the leaf clock: + +| | title clock | leaf clock | **leaf / title** | +|---|---|---|---| +| f6 | 1.0714 u/fr | 0.5137 u/fr | **0.4795** | +| f6b | 2.1429 u/fr | 1.0000 u/fr | **0.4667** | + +2.7% apart, ≈ **½**. The two runs differ 2× in frames and agree on the ratio. + +🔴 **I over-withdrew unit 8's 0.514 last iteration.** Frames-based rates were the +thing that failed, and I threw the ratio out with them. **The ratio was right:** +`leaf ≈ 0.5 × title` now has two independent runs and a declared calibration +behind it. The port should reinstate it, not drop it. + +## What this says about F6 + +The sweep is switched on by its parent at **`t=70`**, full at **`t=100`** — not at +`t=0`, and not by anything to do with the plate. It never plays its `238…250` +exit because the title clock freezes inside `[160,236]` +([`ui-clock-freezes-at-settle.md`](structures/ui-clock-freezes-at-settle.md)), +which is why it loops indefinitely on a held title. + +## Not settled + +* 🟡 **The element I have been calling "the plate" is unidentified** — it is + tracked by screen position only. The declared plate is at `t≈236`; in-capture it + lands at `t≈137` on this title clock. Until it is named, that 1.7× stays + unexplained and no plate-relative number should be trusted. Unit 9's onset + *fraction* is measured against it, so it inherits this. +* `pteff03a` (`ptloop02`'s leaf) is still absent from every capture. +* Wrap-to-wrap period; F5, untouched.