From 2ce3e188a2e4fbe221f0512703539607f22bdb9d Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Mon, 31 Aug 2026 04:02:33 +0000 Subject: [PATCH] port: the one load-bearing thing in the denominator thread, checked against the port Their substantive point was not about counting: a static record still declares a cycle length, and a nonzero +0x08 against a largest keyframe time of 0 is a real disagreement. That is a rendering question for this port and it had not been asked. Scoped to GP_TITLE, the archive the port exports: 65 nested records, 20 declaring a cycle while every pose sits at t=0, and 0 of those with any element carrying more than one pose. So the declared cycle is visually inert on every one of them. A record whose elements each hold a single pose renders identically whether looped or held, since there is nothing to move between. The port holds nothing still that the disc says moves, and that is now measured rather than assumed. It includes ptbtn11, ptbtn12 and ptbtn13 -- EXTRAS' own buttons -- declaring 120-unit cycles. Had any carried two poses, the port would have been holding a menu button the disc says animates, on the one submenu P5's gate walks. The check cost one scan and the answer could have gone the other way. That is the thread's yield stated honestly. Three rounds of correction ran over an interpretation that was never load-bearing -- the offset stood on both scans throughout, so the cost of being wrong at each step was a paragraph. What came out of it worth having: the population distinction, and this check, which exists because they pushed on what the 1530 MEAN rather than on how they are counted. Their framing of why it was safe is the caveat I would attach to repeating it: nothing the port depends on moved at any point. That made three rounds cheap. It does not make three rounds a good default, and I would not have spent them if a shipped value had been waiting on the outcome. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- .../examples/static_with_cycle.rs | 46 +++++++++++++++++++ docs/port/DECISIONS.md | 40 +++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 crates/sylpheed-export/examples/static_with_cycle.rs diff --git a/crates/sylpheed-export/examples/static_with_cycle.rs b/crates/sylpheed-export/examples/static_with_cycle.rs new file mode 100644 index 00000000..0e8b114c --- /dev/null +++ b/crates/sylpheed-export/examples/static_with_cycle.rs @@ -0,0 +1,46 @@ +//! Do any screens THIS PORT SHIPS carry a record that declares a cycle while all +//! its poses sit at t = 0? +//! +//! The substantive finding from the denominator thread: 1 530 nested records +//! disc-wide are timed with every pose at t = 0 and still declare a nonzero +//! `+0x08`. A static record that declares a cycle length is a real thing, not a +//! counting artefact — so the question for the port is whether it holds one of +//! those still while the disc says it cycles. +//! +//! Scoped to `GP_TITLE`, because that is the archive the port exports. +use sylpheed_formats::{pak, ratc, ui_layout}; + +fn main() { + let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into()); + let ar = pak::PakArchive::open(format!("{root}/dat/GP_TITLE.pak")).expect("GP_TITLE.pak"); + let (mut total, mut hits, mut multipose) = (0usize, 0usize, 0usize); + for (i, e) in ar.entries().iter().enumerate() { + let Ok(by) = ar.read(e) else { continue }; + if !ratc::is_ratc(&by) { continue } + let Some(b) = ui_layout::parse_build(&by) else { continue }; + for (name, &(o, s)) in &b.records { + if o + 12 > by.len() || o + s > by.len() || &by[o..o + 4] != b"RATC" { continue } + let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else { continue }; + let maxt = lb.elements.iter() + .flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)).max().unwrap_or(0); + let len = ui_layout::loop_length_units(&by[o..o + s]).unwrap_or(0); + total += 1; + if maxt == 0 && len > 0 { + hits += 1; + // A cycle can only produce motion if there is more than one pose + // to move between. All-at-t=0 with a single keyframe per element + // is visually inert however it is played. + let kf: usize = lb.elements.iter().map(|el| el.keyframes.len()).sum(); + let multi = lb.elements.iter().filter(|el| el.keyframes.len() > 1).count(); + if multi > 0 { multipose += 1 } + println!(" entry {i:>2} {name:<16} {len}-unit cycle, {kf} keyframe(s) \ +across {} element(s), {multi} with >1 pose", lb.elements.len()); + } + } + } + println!("\n {total} nested record(s) in GP_TITLE; {hits} declare a cycle while static."); + println!(" Of those, {multipose} have an element with MORE THAN ONE pose -- the only"); + println!(" ones where looping could differ visibly from holding. A record whose"); + println!(" elements each carry a single pose renders identically either way, so a"); + println!(" declared cycle there is inert rather than a defect."); +} diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index ecd3ae72..a09a09e6 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -310 sections. Search this before re-deriving anything. +311 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -321,6 +321,7 @@ dies, which is what this file is for. * [🔴 My falsifier never identified the offset — the half I called a formality did](#my-falsifier-never-identified-the-offset--the-half-i-called-a-formality-did) * [The 92.3 %-versus-49.6 % gap: same numerator, and their filter is not applied](#the-923--versus-496--gap-same-numerator-and-their-filter-is-not-applied) * [🔴 Correcting my own correction: none of the 1 530 is a question without content](#correcting-my-own-correction-none-of-the-1-530-is-a-question-without-content) +* [The one load-bearing thing in the denominator thread, checked against the port](#the-one-load-bearing-thing-in-the-denominator-thread-checked-against-the-port) ## P0 — the exporter, 2026-08-28 @@ -15302,3 +15303,40 @@ throughout; every disagreement has been about what they were counting.** equals the largest keyframe time exactly where that time is nonzero, `+0x04` does so **0 %** of the time under either denominator, and the offset identification stands on both scans. + +## The one load-bearing thing in the denominator thread, checked against the port + +Their substantive point was not about counting: **a static record still declares a +cycle length**, and a nonzero `+0x08` against a largest keyframe time of 0 is a +real disagreement. That is a *rendering* question for this port, and it had not +been asked. + +Scoped to the archive the port exports: + +| | | +|---|---| +| nested records in `GP_TITLE` | **65** | +| declaring a cycle while every pose sits at t = 0 | **20** | +| **of those, with any element carrying more than one pose** | **0** | + +✅ **So the declared cycle is visually inert on every one of them.** A record whose +elements each hold a single pose renders identically whether looped or held — +there is nothing to move between. **The port holds nothing still that the disc +says moves**, and that is now measured rather than assumed. + +⚠️ It includes `ptbtn11`/`12`/`13` — EXTRAS' own buttons — declaring 120-unit +cycles. Had any carried two poses, the port would have been holding a menu button +the disc says animates, on the one submenu P5's gate walks. **The check cost one +scan and the answer could have gone the other way.** + +📌 **And this is the thread's whole yield stated honestly.** Three rounds of +correction ran over an interpretation that was **never load-bearing** — the offset +stood on both scans throughout, so the cost of being wrong at each step was a +paragraph. What came out of it that was worth having: the **population +distinction**, and this one check, which exists because they pushed on what the +1 530 *mean* rather than on how they are counted. + +⚠️ Their framing of why it was safe is the caveat I would attach to repeating it: +**nothing the port depends on moved at any point.** That made three rounds cheap. +It does not make three rounds a good default, and I would not have spent them if a +shipped value had been waiting on the outcome.