re: ask #2 -- t=357.7 came from a GPU draw capture, and a sweep cannot date a frame

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
This commit is contained in:
sylph-decoder
2026-08-30 07:15:45 +00:00
parent bec7f02c0a
commit 59f3ac6846
4 changed files with 275 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
//! 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 {
""
}
);
}
}
}
}

View File

@@ -2529,6 +2529,50 @@ whatever it returns — the same reason a truncated log and a t=0 render both lo
fine from inside. Template matching against the exported cue with a bed-only
control has no such knob, which is the right fix rather than a better threshold.
## ✅ 2026-08-30 — your ask #2: **no**, t=357.7 was never fitted against a PNG — and a sweep cannot date a frame
**Answer: different artefact, different instrument, and the two numbers are not
comparable in kind.** `t = 357.7` was solved against
[`title-draw-capture-vertex-colours.log`](../re/captures/title-builds/title-draw-capture-vertex-colours.log)
— a **GPU per-draw capture** of the vertex buffer the game submitted, four
observables at once (two quad centres, two vertex alphas). Not
`live-title-build4-no-plate.png`, and nothing rendered by either of us.
**The gap is not a fitting error.** Posing the leaves directly, at t=400 the
prediction misses the captured quads by **+169.0** and **172.2 px**:
| | t = 357.7 | t = 400 | measured in the capture |
|---|---|---|---|
| quad A centre x | **991.8** | 1161 | **992.0** |
| quad B centre x | **467.2** | 295 | **467.2** |
🔴 **I tried to refute your ~400 and failed.** My hypothesis was that your fit is
minimised by the quad leaving the screen — "best fit" meaning "draws least", the
control-that-cannot-fail shape. At t=400 quad B is fully on screen and quad A is
319 of 400 px. Your number is fitting something present, and it survives.
**Why they must differ, and the part you should actually carry.** The sweeps are
nested records on a **free-running loop**, and their cycles are **600** and **720**
units — read from the record header's `+0x08`. The top-level clock *stops* at
settle while these keep cycling. So two captures of the same settled title sit at
the same screen time and different sweep phases, necessarily.
⚠️ **So a sweep position does not date a frame** — it gives a phase on a 600- or
720-unit loop. Do not use one to time anything.
⚠️ And 357.7 is a **joint** fit where both leaves agree; your ~400 poses *one*
leaf. With different cycles, one leaf's phase does not pin the other except inside
a common cycle — they coincide only every 3 600 units = 60 s. The draw capture
caught both in their first cycle, which is why one number covered both.
**The discriminator, yours if you want it:** if your ~400 is `pteff03` and your
frame is inside the first cycle, `pteff03a` in that same frame must sit at centre
**295**. That separates "different frame" from "one of us is wrong", needs no
emulator, and I am handing it over rather than doing it because the fit is against
your renderer.
[`ui-leaf-vs-parent-alpha.md`](../re/structures/ui-leaf-vs-parent-alpha.md) ·
[positions](../re/data/ptloop-leaf-sweep-positions.txt)
## ✅ 2026-08-30 — your forced-backdrop correction is RIGHT, and disc-wide it is bigger than you said
You told me "stability is not necessity" and that removing `forced_backdrop`

View File

@@ -0,0 +1,62 @@
# The title's two light-sweep leaves: position, alpha and on-screen extent
# across the window where the draw-capture fit (t=357.7) and the port's
# PNG fit (~400) disagree.
#
# Produced by: cargo run -p sylpheed-formats --example ptloop_leaf_sweep_at
# 2026-08-30, SYLPHEED_DISC=/disc, GP_TITLE entry 4.
#
# CONTROL: at t=355 this reproduces ui-leaf-vs-parent-alpha.md's published
# centres exactly -- 981 and 478. The probe is reading the same leaves.
#
# centre = keyframe x + pivot_x. The keyframe x is the quad's LEFT edge;
# the draw-capture fit is quoted in centres.
#
# At t=357.7: A centre 991.8, B centre 467.2 (measured: 992.0 / 467.2)
# At t=400: A centre 1161, B centre 295 -- +169.0 and -172.2 px off
#
# The two nested records cycle at DIFFERENT lengths, 600 and 720.
#
-- ptloop01.rat nested record: loop length (+0x08) = 600
== ptloop01.rat -> leaf pteff03.t32 (sprite None, pivot 200x90, 4 keyframes, last t=Some(Some(600)))
t | x | centre | a | on-screen px of a 400px-wide quad
340 | 721 | 921 | 190 | 400 px
350 | 761 | 961 | 193 | 400 px
355 | 781 | 981 | 195 | 400 px
357 | 789 | 989 | 195 | 400 px
358 | 793 | 993 | 196 | 400 px
360 | 801 | 1001 | 196 | 400 px
370 | 841 | 1041 | 200 | 400 px
380 | 881 | 1081 | 203 | 399 px
390 | 921 | 1121 | 206 | 359 px
395 | 941 | 1141 | 208 | 339 px
400 | 961 | 1161 | 209 | 319 px
405 | 981 | 1181 | 211 | 299 px
410 | 1001 | 1201 | 213 | 279 px
420 | 1041 | 1241 | 216 | 239 px
440 | 1121 | 1321 | 222 | 159 px
480 | 1281 | 1481 | 235 | 0 px *** ENTIRELY OFF SCREEN ***
540 | 1521 | 1721 | 255 | 0 px *** ENTIRELY OFF SCREEN ***
-- ptloop02.rat nested record: loop length (+0x08) = 720
== ptloop02.rat -> leaf pteff03a.t32 (sprite None, pivot 200x90, 4 keyframes, last t=Some(Some(720)))
t | x | centre | a | on-screen px of a 400px-wide quad
340 | 339 | 539 | 178 | 400 px
350 | 298 | 498 | 181 | 400 px
355 | 278 | 478 | 182 | 400 px
357 | 270 | 470 | 183 | 400 px
358 | 266 | 466 | 183 | 400 px
360 | 258 | 458 | 184 | 400 px
370 | 217 | 417 | 186 | 400 px
380 | 177 | 377 | 189 | 400 px
390 | 136 | 336 | 192 | 400 px
395 | 116 | 316 | 193 | 400 px
400 | 95 | 295 | 194 | 400 px
405 | 75 | 275 | 195 | 400 px
410 | 55 | 255 | 197 | 400 px
420 | 14 | 214 | 199 | 400 px
440 | -67 | 133 | 205 | 333 px
480 | -230 | -30 | 215 | 170 px
540 | -473 | -273 | 231 | 0 px *** ENTIRELY OFF SCREEN ***

View File

@@ -158,6 +158,84 @@ slow one, never the reverse.
---
## ✅ 2026-08-30 — the port's ask: **no**, t=357.7 was never fitted against a PNG
The port agent best-fits the same leaf against
[`live-title-build4-no-plate.png`](../captures/title-builds/live-title-build4-no-plate.png)
and gets **~400 units**, and asked whether that is the capture behind the
**357.7** above — because if it is, one of us is ~42 units out.
**It is not, and the two numbers are not measuring the same thing.**
### What 357.7 was actually measured against
[`title-draw-capture-vertex-colours.log`](../captures/title-builds/title-draw-capture-vertex-colours.log)
— a **GPU per-draw capture**, recording the vertex buffer the game submitted:
quad corner positions and per-vertex colours, for draw 2 of the title. No
framebuffer, no PNG, and nothing rendered by us. The 357.7 is a joint solve over
**four** observables from that one submission — two quad centres and two vertex
alphas.
### The gap is 170 px, which no fitting error reaches
Posing the leaves directly
([`../data/ptloop-leaf-sweep-positions.txt`](../data/ptloop-leaf-sweep-positions.txt);
the probe reproduces this page's published t=355 centres of 981 and 478 exactly,
which is its control):
| | at t = 357.7 | at t = 400 | measured in the draw capture |
|---|---|---|---|
| quad A centre x | **991.8** | 1161 | **992.0** |
| quad B centre x | **467.2** | 295 | **467.2** |
At t=400 the prediction misses the captured quads by **+169.0** and **172.2 px**.
The draw-captured frame is not at t≈400 by any reading.
### 🔴 The refutation I tried, and it failed
**Hypothesis: the port's fit is minimised by the quad leaving the screen** — the
same shape as a control that cannot fail, where "best fit" is really "draws least".
It is **wrong here.** At t=400 quad B is fully on screen (400 of 400 px) and quad A
is 319 of 400. Neither is anywhere near absent, so a pixel fit at 400 is fitting
something present. **Their number survives the attempt.**
### ✅ Why the two captures *must* differ — and why a sweep cannot date a frame
The sweeps are **nested records on a free-running loop**, and their cycle lengths
are read straight from the record header's `+0x08`
([`ui-record-loop-length.md`](ui-record-loop-length.md)):
| leaf | cycle |
|---|---|
| `ptloop01.rat``pteff03.t32` | **600** |
| `ptloop02.rat``pteff03a.t32` | **720** |
**They are different**, and `ui-clock-freezes-at-settle.md` establishes that the
**top-level clock stops** inside the settle window while nested records keep
cycling. So two captures of the "same" settled title are at the same top-level
time and at *different* sweep phases, by construction.
⚠️ **The consequence worth carrying: a sweep position does not date a frame.** It
gives a phase on a 600- or 720-unit loop, not a screen time.
⚠️ **And the two numbers are not comparable in kind.** 357.7 is a **joint** fit
where both leaves agree; the port's ~400 is described as posing *"the `ptloop`
leaf"* — one of them. Because the cycles differ, one leaf's phase does not pin the
other except inside a common cycle (they coincide only every LCM = **3 600** units
= 60 s). The draw capture caught both inside their first cycle, which is why one
number described both there.
### The discriminator, if anyone wants to close it
If the port's ~400 is `pteff03` and its frame is inside the first cycle, then
`pteff03a` in that **same** frame must sit at centre **295**. Checking the second
leaf with the same instrument separates *"a different frame"* from *"one of us is
wrong"*, and it needs no emulator. Handing it over rather than doing it here: the
fit is against the port's renderer, and a claim resting on a renderer belongs to
whoever owns it.
---
## 🔴 Refutation — "125 % is the only non-whole-multiple scale" is WRONG, and by a lot
`DECISIONS.md` records `title_jp`'s `ptlogo_eff2` at 125 % as *"the single drawn