From 0e41f5381da6788e2e31c03d7accc51f054878ec Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 21:27:54 +0000 Subject: [PATCH] port: take formats-pin-2026-08-30b and stop owning the +0x08 read The tag was cut within the iteration, so screen.rs now calls ui_layout::loop_length_units and its local RATC guard and byte read are deleted. One line, as predicted -- and the doc comment promising that deletion is the only reason a temporary reading did not quietly become permanent. A pin bump moves the whole crate, not one function, and this pin is recorded load-bearing, so both commits between the tags were read before taking it: b5df02a adds the public function, d020845 is comment-only -- two 'fixed code under an unfixed description' corrections. No behavioural change in either, and the oracle RMSEs confirm it: main_menu 13.21, extras 13.38, title 14.16, all identical across the bump. d020845 is worth noting for what it is. The rest override's comment still claimed it tested the shifted time reading that the record-layout fix had refuted, and a continue branch was documented with the pre-fix rule. Both are the same failure this port hit in spin_period_units: a doc comment describing the rule the body no longer implements. Three instances now, across two agents and two languages. examples/record_loop_control.rs deliberately did NOT follow the API. The moment a control calls the thing it exists to check it stops being a control and becomes the API tested against itself; the falsifier means something only because the reading is independent. Re-run at the new pin, unchanged. So the port holds one copy of this reading instead of two, and it is the copy whose job is to disagree. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- Cargo.lock | 4 +- crates/sylpheed-export/Cargo.toml | 2 +- .../examples/record_loop_control.rs | 13 +++++-- crates/sylpheed-export/src/screen.rs | 27 ++++--------- docs/port/DECISIONS.md | 38 ++++++++++++++++++- 5 files changed, 57 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e655ebf9..17a66a4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4627,7 +4627,7 @@ dependencies = [ "image", "serde", "serde_json", - "sylpheed-formats 0.1.0 (git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-08-30)", + "sylpheed-formats 0.1.0 (git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-08-30b)", ] [[package]] @@ -4651,7 +4651,7 @@ dependencies = [ [[package]] name = "sylpheed-formats" version = "0.1.0" -source = "git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-08-30#c8d3a6a15d5feb0416b2b589cb8e0c442f7422d3" +source = "git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-08-30b#b5df02a4f74c80b93b93cd1a26296ee08040d826" dependencies = [ "anyhow", "binrw", diff --git a/crates/sylpheed-export/Cargo.toml b/crates/sylpheed-export/Cargo.toml index b941fd21..81334496 100644 --- a/crates/sylpheed-export/Cargo.toml +++ b/crates/sylpheed-export/Cargo.toml @@ -81,7 +81,7 @@ license.workspace = true # geometry -- does NOT reach this port from here: `sylpheed-cli` builds from the # WORKSPACE crate, so the reference renderer stays unrotated until the tag lands # on `main`. This bump is for the parser, not for the renderer. -sylpheed-formats = { git = "https://git.mc02.dev/fabi/Sylpheed.git", tag = "formats-pin-2026-08-30" } +sylpheed-formats = { git = "https://git.mc02.dev/fabi/Sylpheed.git", tag = "formats-pin-2026-08-30b" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/crates/sylpheed-export/examples/record_loop_control.rs b/crates/sylpheed-export/examples/record_loop_control.rs index f9e5e547..eb24796d 100644 --- a/crates/sylpheed-export/examples/record_loop_control.rs +++ b/crates/sylpheed-export/examples/record_loop_control.rs @@ -8,10 +8,15 @@ //! would fix it is decoded in an *example* and a *test* on the Decoder's branch //! and **exposed in `sylpheed_formats`' public API on no ref at all**. //! -//! It is still reachable: `parse_build` publishes each record's `(offset, size)`, -//! so reading a big-endian `u32` at `+0x08` of a span whose magic is `RATC` is -//! consuming a delivered finding, not decoding a format. What must not be -//! consumed on trust is the READING. So this re-runs both of their controls: +//! ✅ **Since then the crate exposes it** — `ui_layout::loop_length_units`, taken +//! at `formats-pin-2026-08-30b` — and `screen.rs` has deleted its local copy. +//! +//! 🔴 **This file deliberately did NOT follow it.** The read below is still the +//! raw four bytes, because the moment a control calls the API it is meant to +//! check, it stops being a control and becomes the API tested against itself. It +//! is the independent reading that makes the falsifier mean anything. +//! +//! So this re-runs both of their controls: //! //! * **the falsifier** — `+0x08 < max keyframe time` must never occur; an //! animation cannot restart before its own last pose; diff --git a/crates/sylpheed-export/src/screen.rs b/crates/sylpheed-export/src/screen.rs index f98ad425..003f56aa 100644 --- a/crates/sylpheed-export/src/screen.rs +++ b/crates/sylpheed-export/src/screen.rs @@ -108,18 +108,6 @@ pub struct FocusElement { pub keyframes: Vec, } -/// A nested record's declared cycle length, or `None` if the span is not one. -/// -/// Guarded rather than trusted: the magic is checked and the header must fit, -/// because an offset that has drifted returns a plausible number otherwise. -fn record_loop_units(bundle: &[u8], off: usize, size: usize) -> Option { - let span = bundle.get(off..off.checked_add(size)?)?; - if span.len() < 12 || &span[..4] != b"RATC" { - return None; - } - Some(u32::from_be_bytes(span[8..12].try_into().ok()?)) -} - #[derive(Serialize)] pub struct Focus { /// The `.rat` leaf this came from, e.g. `ptbtn01f.rat`. @@ -140,11 +128,12 @@ pub struct Focus { /// their own last pose**; on the eight records this port animates, seven /// exact and `ptbtn00f` the one hold. /// - /// ⚠️ Read here rather than through `sylpheed_formats` because the field is - /// exposed in **no public API on any ref** — it lives in an example and a - /// test. `parse_build` publishes each record's `(offset, size)`, so this is - /// four big-endian bytes at a documented offset inside a span whose magic is - /// checked, not a second decoder. **Delete it the day the crate exposes it.** + /// ✅ **The port no longer owns this reading.** For one iteration `screen.rs` + /// held its own guard and byte read, because the field was decoded in an + /// example and a test and exposed in no public API on any ref. It is now + /// `ui_layout::loop_length_units`, taken at `formats-pin-2026-08-30b`, and + /// the local copy is deleted — the doc comment that promised that deletion + /// is the only reason it did not quietly become permanent. pub loop_length_units: Option, /// Back-to-front, in the leaf's own declaration order. pub elements: Vec, @@ -489,7 +478,7 @@ pub fn export_build( } else { Some(Focus { record: rec.to_string(), - loop_length_units: record_loop_units(bundle, off, size), + loop_length_units: ui_layout::loop_length_units(&bundle[off..off + size]), elements: fes, }) }) @@ -550,7 +539,7 @@ pub fn export_build( if !fes.is_empty() { focus = Some(Focus { record: rec, - loop_length_units: record_loop_units(bundle, off, size), + loop_length_units: ui_layout::loop_length_units(&bundle[off..off + size]), elements: fes, }); } diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index ba1d1fc2..2d105f57 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -239 sections. Search this before re-deriving anything. +240 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) @@ -250,6 +250,7 @@ dies, which is what this file is for. * [A refutation attempt on the fade numbers — it survives, from a third reader](#a-refutation-attempt-on-the-fade-numbers--it-survives-from-a-third-reader) * [The walk is checked too, and "only the ring moves" tested against my own renderer](#the-walk-is-checked-too-and-only-the-ring-moves-tested-against-my-own-renderer) * [The `+0x08` ask came back answered — and is not consumable yet](#the-0x08-ask-came-back-answered--and-is-not-consumable-yet) +* [The pin moves to `formats-pin-2026-08-30b`, and the port stops owning `+0x08`](#the-pin-moves-to-formats-pin-2026-08-30b-and-the-port-stops-owning-0x08) ## P0 — the exporter, 2026-08-28 @@ -12483,3 +12484,38 @@ big-endian read. So the deletion my doc comment promises is a one-line switch. branch would replace a deliberate pin with an incidental one — and `BLOCKED.md` already records this pin as load-bearing. **Asked for a tag; keeping the local read, which is guarded and controlled, until one exists.** + +## The pin moves to `formats-pin-2026-08-30b`, and the port stops owning `+0x08` + +The tag was cut within the iteration, so the deletion the doc comment promised is +done: `screen.rs` calls `ui_layout::loop_length_units` and its local `RATC` guard +and byte read are **gone**. One line, exactly as predicted — the promise in the +comment is the only reason a temporary reading did not quietly become permanent. + +**What the pin actually brings, checked before taking it.** A pin bump moves the +whole crate, not one function, and this one is recorded load-bearing, so the two +commits between the tags were read rather than assumed: + +| | | +|---|---| +| `b5df02a` | adds the public `loop_length_units` | +| `d020845` | **comment-only** — two "fixed code under an unfixed description" corrections | + +No behavioural change in either. `d020845` is worth noting for what it is: the +`rest` override's stated purpose was retired by the record-layout fix and the +comment still claimed it tested the shifted reading, and a `continue` branch was +documented with the pre-fix rule. **Both are the same failure this port hit in +`spin_period_units` — a doc comment describing the rule the body no longer +implements.** Three instances now, across two agents and two languages. + +### 🔴 The control did NOT follow the API, on purpose + +`examples/record_loop_control.rs` still reads the raw four bytes. **The moment a +control calls the API it exists to check, it stops being a control and becomes +the API tested against itself.** The falsifier — 0 of 1 781 records declaring +less than their own last pose — means something only because the reading is +independent of the crate's. Re-run at the new pin: unchanged, 7 exact and +`ptbtn00f` the one hold. + +📌 So the port now holds **one** copy of this reading instead of two, and it is +the copy whose job is to disagree.