port: the plate's lateness is the unit, not our clock -- and the splash blur is an asset we already draw

H3, the PRESS (A) plate. Established which half it is, as the brief asked, and
the answer moved once during the iteration -- both readings are recorded because
the first one was confident and wrong.

Eliminated, ours:

  rest.t      not in the causal path. The plate's ARRIVAL is a declared keyframe
              (transparent to t=214, opaque at t=236); rest.t=236 only picks
              where `holding` parks it, and 236 is that ramp's own peak.
  clock origin  the two builds share one clock exactly -- 85 of 85 filmed title
              frames have view_units == overlay_units to 3 dp.

NOT eliminated, the Decoder's: the unit->seconds constant. I first ruled it out
"by sign" using the emulator's 28.1 fps presentation rate. That conflates a
wall-clock conversion with units-per-game-frame; the correction is written down
rather than edited away. The Decoder's splash draw capture (3cc1b51) then gave
an independent handle on it, from a screen with nothing to do with the plate:
three intervals of the publisher splash all read 5 units per guest frame against
the declared 2. At 30 fps the plate would land at 1.57 s rather than 3.93 s --
this port ~2.4 s late, the right magnitude for what the human saw. It collides
with title-plate-delay-measured.md (~55 units/s) by a factor of 2.7. Both are
oracle measurements; asked in BLOCKED.md H3, not picked between.

Finding 4, the splash blur. The Decoder found no post-process pass; the human
says the logos go blurred -> sharp in about a second. Those resolve each other:
every logo ships a pre-blurred copy ~21x20 px larger and concentric, drawn alone
first and crossfaded out as the sharp logo fades in.

  🔴 So "the port applies no blur at all" -- in BLOCKED.md H2 and in the
  play-test -- is FALSE, and it came from our side. The port draws all seven
  quads of the developer splash, companions included, and always has. Shown by a
  frozen sweep 3 units a step: companions alone at u=3..15, all seven at
  u=18..42, sharp only from u=45.

The instrument had to be fixed before any of this counted. `--film` scheduled
frame n for n*interval and reported nothing; one 1280x720 save_png costs ~0.24 s
under llvmpipe, so a request for 0.05 s delivered 247 frames in 60 s where 1200
were asked for -- an achieved 4.1 fps against a requested 20 -- and the deficit
accumulated silently into the frame index. It now writes <film>_frames.tsv with
the second each frame was ACTUALLY taken at, and prints achieved against
requested. The schedule is deliberately not rebased onto _elapsed: catching up
would hide the shortfall.

check-all now reimports the Godot project first. `port/.godot/` is gitignored, so
merging a commit that adds a `class_name` leaves a stale class cache and the
whole project fails to parse -- which is exactly what merging the human's
gamepad.gd did in this container.

Refutation attempts, four:
  survived  the title's settle window [160, 236] -- our exporter still computes
            [160, 236, 198] under the corrected record layout
  survived  the Decoder's Q0/Q7 quad mapping -- this export's declared rects
            reproduce the guest's vertex stream to ~2 px and ~4 px
  refuted   "ptlogo1 rests at t=251 and stops moving at t=42" -- rest.t is now
            42. The record-layout fix repaired the very element that was the
            headline evidence for rest.t != settle. The conclusion survives on
            other elements; the example does not
  refuted   ours: "ptbtn00 reaches a=255 at t=238" (it is 236, so 118 units not
            120) and "pteff00 is 7 % opaque at 243" (it is 0 %; that quad clears
            by t=16 and 261..269 is the fade-OUT, not a fade-in)

Not settled: which of the two oracle rates is right; whether the t=118 or the
t=160 anchor is what the glyph counter saw; whether the game holds the blurred
companion longer than we do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
This commit is contained in:
Sylpheed port agent
2026-09-01 16:35:43 +00:00
parent 6db49f5a24
commit 6eccfa84d8
5 changed files with 427 additions and 17 deletions

View File

@@ -34,9 +34,9 @@
"screen": "press_start",
"clock": "shared",
"why": "MEASURED, 2026-08-29, docs/re/title-plate-delay-measured.md on branch auto/no-disc-and-menu-captures at 5b0a6e6 (NOT on main when this was written). The boot title shows build 4 ALONE and the `PRESS (A) BUTTON` plate -- build 2 -- arrives later. This is the ONE case in the port where two builds are drawn at once.",
"no_constant_why": "THERE IS NO AUTHORED DELAY HERE, AND THERE WAS ONE FOR ONE ITERATION. The first version of this block carried `after_settle_seconds: 2.13`, taken from the RE agent's instruction. The port refuted that instruction with arithmetic off the disc -- build 2 has a group of its own, and starting it at settle put the plate 3.97 s late -- and the corrected answer needs no constant at all: BOTH BUILDS RUN ON ONE CLOCK, STARTED TOGETHER, and the plate arrives at its own declared t=238. `clock: \"shared\"` is that, spelled out rather than implied by the absence of a delay field. 📌 SOURCES, added 2026-09-01 in the uncited-why backfill: the plate's arrival is docs/re/title-plate-delay-measured.md and its pulse is docs/re/structures/plate-pulse-measured.md.",
"arithmetic_why": "Why one clock reproduces the measurement, checked against this export rather than taken on trust: build 4's visible build-in ends at t=118, where `pteff01`, `pteff02` and `ptlogoall_eff` finish together; `ptbtn00` reaches alpha 255 at t=238; the difference is 120 units = 2.000 s at 60 units/s. The oracle measured 2.138 s and 2.132 s. The gap is presentation rate: 120 units in 2.135 s is 56.2 units/s, i.e. the emulator presenting at 28.1 fps against a nominal 30, and the corpus had independently measured the idle title at 28.5 fps before these runs. 📌 SOURCE, added 2026-09-01: the pulse period and its phase behaviour are in docs/re/structures/plate-pulse-measured.md and docs/re/structures/plate-pulse-phase-lock.md, with the raw series in docs/re/data/plate-pulse-timeseries.txt. ✅ AUDITED 2026-09-01: the corpus's 28.5 fps is a genuinely independent leg -- a different quantity (idle-title presentation rate), measured BEFORE these runs, so it could have come out disagreeing. It agrees to 1.4 %. That is corroboration rather than a restatement, which is the distinction the week's audits were about.",
"the_premise_that_failed_why": "The port's own, and it is worth keeping because it will bite again: `rest.t` IS NOT WHEN A SCREEN SETTLES. It is the last hold keyframe before the exit. `ptlogo1` has rest.t=251 and stops MOVING at t=42, then creeps 5 px and 31 alpha steps over the next 209 units. Reading rest.t as the settle put build 4's arrival at 4.35 s instead of 1.97 s, and every reconciliation computed from it came out wrong by exactly that error. `ScreenView.settle_time()` still uses rest.t -- see docs/port/BLOCKED.md.",
"no_constant_why": "THERE IS NO AUTHORED DELAY HERE, AND THERE WAS ONE FOR ONE ITERATION. The first version of this block carried `after_settle_seconds: 2.13`, taken from the RE agent's instruction. The port refuted that instruction with arithmetic off the disc -- build 2 has a group of its own, and starting it at settle put the plate 3.97 s late -- and the corrected answer needs no constant at all: BOTH BUILDS RUN ON ONE CLOCK, STARTED TOGETHER, and the plate arrives at its own declared t=236 (CORRECTED 2026-09-01 from t=238, which is the last opaque frame rather than the arrival). `clock: \"shared\"` is that, spelled out rather than implied by the absence of a delay field. 📌 SOURCES, added 2026-09-01 in the uncited-why backfill: the plate's arrival is docs/re/title-plate-delay-measured.md and its pulse is docs/re/structures/plate-pulse-measured.md. \ud83d\udd34 AND `clock: \"shared\"` IS AUTHORED FROM OUR OWN ARITHMETIC, NOT MEASURED. Nobody has watched whether build 2's group starts with build 4's; it is the reading that reconciles the oracle's 2.13 s IF the settle anchor is t=118. See docs/port/plate-arrival-halves.md and BLOCKED.md H3.",
"arithmetic_why": "Why one clock reproduces the measurement, checked against this export rather than taken on trust: build 4's effect quads `pteff01`, `pteff02` and `ptlogoall_eff` end their ramps together at t=118; `ptbtn00` reaches alpha 255 at t=236; the difference is 118 units = 1.967 s at 60 units/s. The oracle measured 2.138 s and 2.132 s. The gap is presentation rate: the emulator presents at 28.1 fps against a nominal 30, and the corpus had independently measured the idle title at 28.5 fps before these runs. 🔴 CORRECTED 2026-09-01: this said `ptbtn00` reaches 255 at t=238 and that the difference is 120 units = 2.000 s. It reaches 255 at t=236 and HOLDS to 238, so 238 is the last opaque frame, not the arrival; 236 - 118 = 118. The port printed the contradiction in one sentence on every boot. The correction moves the reconciliation by 0.033 s and overturns nothing -- see docs/port/plate-arrival-halves.md. 🔴 AND THE ANCHOR IS NOW OPEN. The oracle defines \"title settled\" operationally, as its glyph counter first reading the no-plate value 154. This export offers TWO anchors 42 units apart: t=118 (the effect quads) and t=160 (`ptcopyright` at full alpha -- the LAST element to finish building in, and the only one made of glyphs). This line picked 118, while `ScreenView.settle_time()` returns 160 and the boot prints `settles at t=160`, so one binary holds both. Asked in BLOCKED.md H3; not guessed here. 📌 SOURCE: the pulse period and its phase behaviour are in docs/re/structures/plate-pulse-measured.md and docs/re/structures/plate-pulse-phase-lock.md, with the raw series in docs/re/data/plate-pulse-timeseries.txt. ✅ AUDITED 2026-09-01: the corpus's 28.5 fps is a genuinely independent leg -- a different quantity (idle-title presentation rate), measured BEFORE these runs, so it could have come out disagreeing. It agrees to 1.4 %.",
"the_premise_that_failed_why": "The port's own, and it is worth keeping because it will bite again: `rest.t` IS NOT WHEN A SCREEN SETTLES. It is the last hold keyframe before the exit. Reading it as the settle put build 4's arrival at 4.35 s instead of 1.97 s, and every reconciliation computed from it came out wrong by exactly that error. `ScreenView.settle_time()` still uses rest.t -- see docs/port/BLOCKED.md. 🔴 THE EXAMPLE THIS CITED IS GONE, THOUGH THE CONCLUSION IS NOT. It read \"`ptlogo1` has rest.t=251 and stops MOVING at t=42\". In the CURRENT export `ptlogo1.rest.t` is 42 -- equal to when it stops moving. The record-layout fix repaired precisely that element, and the entry was never re-derived under it (REFUTED.md now carries this at 🟡 ⟨our-reader⟩). rest.t is still wrong for transients -- `ptlogo_back2eff1` is a two-frame flash whose rest.t=54 is the flash PEAK -- and for `pteff00`, whose rest.t=16 sits at the end of the fade-FROM-black while a fade-TO-black runs 261..269. Re-derived 2026-09-01: docs/port/plate-arrival-halves.md. 🔴 AND IT IS NOT THIS DEFECT'S CAUSE. The plate's ARRIVAL is a declared keyframe (transparent to t=214, opaque at t=236), not a rest pose; rest.t=236 only chooses where `holding` parks it, and 236 is that ramp's own peak. Confirmed on a filmed boot with rest.t untouched: the onset is bracketed within one frame of 214.",
"scope_why": "Attached to the BOOT STEP, not to the `title` screen, and that is deliberate. What was measured is the boot title. Whether the title shows the plate when it is REACHED AGAIN -- by (B) from the main menu, or after the attract movie -- is not measured, and putting the overlay on the screen would quietly claim it is. 📌 SOURCE, added 2026-09-01: the plate belongs to the boot's overlay step rather than to the title screen because its arrival is measured against the boot clock -- docs/re/title-plate-delay-measured.md.",
"no_pulse_why": "The port draws the plate arriving and then holding. It does not pulse it. The RE agent identifies the pulse as the plate's FOCUS RECORD `ptbtn00f` -- a glow ramping 0x00 to 0x50 and back, t=6..105 -- not as a loop of `ptbtn00`'s own group, which was the port's earlier reading and was wrong. Looping that record is a candidate the port has NOT taken: its group is 105 timed units plus an AUTHORED 24-unit exit ramp, and hitting the measured 2.24 s mean requires composing that authored constant with a loop assumption, which is tuning rather than measuring. Filed in BLOCKED.md. 📌 SOURCE, added 2026-09-01: docs/re/structures/plate-pulse-measured.md, and the phase-lock caveat that bounds what a gated capture can show is docs/re/structures/plate-pulse-phase-lock.md."
},

View File

@@ -105,8 +105,8 @@ method finding that applies to the Decoder's harness as much as this one:
| # | ask | why the port cannot answer it |
|---|---|---|
| **H1** | **Does a held direction REPEAT in the menus, and at what rate?** Initial delay and repeat interval, for the d-pad and for the left stick separately if they differ. | Behavioural. The port now does **one step per deflection**`authored`, and deliberately the conservative half: it cannot run away and it invents no rate. If the game repeats, a human will feel it as *"I have to flick it again"*. |
| **H2** | **The splash blur/fade-in does not match the game.** A human watching both says the game's is **more pronounced** than the port's. | Needs a capture of the publisher/developer splash across its fade, at a known frame cadence. The port's ramp comes from the declared keyframes; whether the game applies a **blur** on top — and whether what we read as a fade is one — is not answerable from the export. |
| **H3** | **The `PRESS Ⓐ` plate arrives LATE.** The port raises it at `t=236`, derived `238 118 = 120 units = 2.000 s`. | 🟡 **Half of this may be the port's, and that half must be settled here first.** Three candidate causes: the **unit→seconds constant** (Decoder's), the **clock origin** — do builds 2 and 4 really start together? — and **`rest.t`** (both ours). `REFUTED.md` records that believing `rest.t` already put a plate **3.97 s late** once. Establish which half it is *before* asking, and say how. |
| **H2** | **ANSWERED 2026-09-01, and the port's own premise was wrong.** The Decoder found **no post-process pass** on either splash (`auto/frame-blend-draw-path` `3cc1b51`). The blur is **in the asset**: every logo ships a pre-blurred copy ~21×20 px larger and concentric, drawn alone first and crossfaded out as the sharp logo fades in — which is the human's *"blurred to clear/sharp, in about a second"*, with no shader. | 🔴 **"The port applies no blur at all" was FALSE and this page said it.** The port draws all seven quads of the developer splash, the three companions included, and always has. Verified by a frozen sweep, 3 units a step: companions alone at u=315, all seven at u=1842, sharp only from u=45. Writing down what the renderer was *believed* to do instead of running it is what produced the claim. What remains is the **extent**, and it is the same unit question as H3 — asked there, once. See [`plate-arrival-halves.md`](plate-arrival-halves.md). |
| **H3** | 🟢 **NARROWED 2026-09-01, port half eliminated. The ask is now one question: what does your "title settled" anchor correspond to on the declared timeline?** You define it operationally — *glyph counter first reads its no-plate value 154*. Our export offers **two** anchors 42 units apart: **t=118** (`pteff01`, `pteff02`, `ptlogoall_eff` end their ramps together) and **t=160** (`ptcopyright` reaches full alpha — the *last* element to finish building in, and the only one made of **glyphs**). At 118 the shared-clock reading reproduces your 2.135 s to 8 %; at 160 it misses by 41 % and `clock: "shared"` collapses. 🔴 **AND A SECOND, SHARPER HALF, ADDED AFTER YOUR SPLASH CAPTURE LANDED: your two captures imply keyframe rates a factor of 2.7 apart.** `splash-quad-timeline.txt` gives **5 units per guest frame** on three independent intervals of the publisher splash (companion onset→logo onset, companion onset→peak, logo onset→full: 3 frames each, against 15 declared units each) — against the declared **2**. At 30 fps that is ~150 units/s, the plate would land at **1.57 s** instead of 3.93 s, and this port would be **~2.4 s late**, which is the right magnitude for what the human saw. But `title-plate-delay-measured.md` implies **~55 units/s** from the same game. Both are yours; we cannot pick. ⚠️ Caveat we can see from here: the splash capture's frame index **skips** (5, 8, 12 are absent from Q7's eight draws), and whether those are unsubmitted quads or dropped presents changes the arithmetic. | All three candidates the play-test named are eliminated, with evidence, in [`plate-arrival-halves.md`](plate-arrival-halves.md) — derived from `export/` at port `HEAD` `6db49f5`, HANDOFF at `9ca1eb5`. **`rest.t`**: not in the causal path — the plate's onset is a declared keyframe (transparent to t=214, opaque at t=236), and rest.t only picks the parking pose. **Clock origin**: the two builds share one clock exactly — 85 of 85 filmed title frames have `view_units == overlay_units` to 3 dp. **Unit constant**: eliminated by *sign* — making the port earlier needs units/s **above** 60, while 28.1 fps presentation and 28.5 fps idle title both say the game's units run slower. And the port is **0.17 s early** on the interval you measured (1.967 s vs 2.138/2.132) and **between your two runs** on first-drawn→plate (3.933 s vs 3.781/4.263). 🔴 Also corrected: `ptbtn00` reaches a=255 at **t=236**, not 238 — so the cited `238 118 = 120` is `236 118 = 118`. |
**On H2, three things the port can say that narrow it**, none of which settle it:

View File

@@ -0,0 +1,315 @@
# H3 — the `PRESS Ⓐ` plate: which half the lateness is in
**Status:** the port's half is **eliminated**; the Decoder's half is **live and now has a number**.
Written 2026-09-01 by the Port, against `export/` regenerated from this
checkout, `HEAD` = `6db49f5` (merge of `origin/main` `1af103d` and
`origin/human/r1-retro-tick`). HANDOFF on this branch answers `9ca1eb5`.
The [play-test](../agents/PLAYTEST-2026-09-01.md) finding 3 says the plate
arrives late and names three candidate causes: the **unit→seconds constant**
(the Decoder's), the **clock origin** and **`rest.t`** (both ours). The brief
asks which half it is, and how that was established. This page is the answer.
> **Short version: the two candidates that were ours are eliminated, and the one
> that is the Decoder's is not.** The port renders the plate exactly where its
> own export declares it, on a clock verified identical to build 4's frame by
> frame — so neither `rest.t` nor the clock origin can be the cause. The
> **unit→seconds constant** survives, and a capture the Decoder landed the same
> day puts it at **5 units per guest frame against the declared 2**, which would
> make this port ~2.4 s late — the right magnitude. That reading collides with
> the oracle's own plate measurement by a factor of 2.7, so it is asked, not
> acted on.
## What the export actually declares
`export/screens/title/press_start.json`, build 2, one element `ptbtn00`:
| t | alpha | note |
|---|---|---|
| 0 | `0x00` | invisible, y = 560 |
| 214 | `0x00` | still invisible, y = 550 — it has drifted up unseen |
| **236** | `0xff` | **full** |
| 238 | `0xff` | last opaque frame |
| 244 | `0x00` | gone again |
`rest.t = 236`. `settle_window = [214, 236, 225]`.
🔴 **The corpus says the plate reaches `a=255` at `t=238`. It reaches it at
`t=236`.** 238 is the last frame at full alpha, not the arrival. Every
derivation quoting `238 118 = 120 units = 2.000 s` is really `236 118 =
118 units = 1.967 s`. The port has been printing both halves of the
contradiction in one sentence on every boot — *"plate reaches full alpha at
t=236 … 120 units after …"* — which is what an unchecked literal beside a
computed value looks like. Corrected in `boot.gd` and in `authored/flow.json`.
It moves the reconciliation by 0.033 s and overturns nothing.
## Candidate 1 — `rest.t`. **Eliminated.**
`rest.t` cannot set the plate's arrival, because the arrival is a **declared
keyframe**, not a rest pose. `ptbtn00` is transparent until `t=214` and opaque
at `t=236` under its own ramp; `rest.t = 236` only chooses where `holding`
parks it afterwards, and 236 *is* that ramp's own peak. Setting `rest.t` to any
other value moves where the plate stops, never when it starts.
Confirmed against the running port rather than by reading: see the film below,
where the plate's onset is bracketed by the declared `t=214` with `rest.t`
untouched.
⚠️ This does **not** rehabilitate `rest.t` generally. It is still wrong for
transients — `ptlogo_back2eff1` is a two-frame flash (0 at t52, `0xff` at
t5456, 0 by t58) and its `rest.t = 54` is the flash *peak*, so `rest()` leaves
five of these burning at once. It is simply not in this defect's causal path.
## Candidate 2 — the clock origin. **Eliminated, and measured.**
Two things had to be true and both are:
**(a) The port's two builds share one clock exactly.** `_advance` sets
`view.time_units = 0.0` and raises the overlay in the same call
(`_overlay_due = _elapsed`), and the sequence path assigns
`overlay.time_units = view.time_units` outright rather than integrating a second
delta. Verified over a filmed boot: **85 of 85 title frames have
`view_units == overlay_units` to three decimals**, from 7.812 to 679.182. There
is no drift and no offset to find.
**(b) The title step begins when the previous step ends.** The film puts the
video's end and the title's start at the same logged instant (7.86 s / 7.92 s
across two runs), with no black hold between them.
## Candidate 3 — the unit→seconds constant. **NOT eliminated. It is the live one.**
🔴 **This section said "eliminated by sign" and that was wrong.** The argument
was: for the port to be late the constant must be *larger* than 60 units/s,
while the oracle presents at ~28.1 fps and the corpus measured the idle title at
28.5 fps — both slower than nominal, so the game's units run slower, not faster.
**That conflates two different quantities.** A presentation rate converts a
*measured wall-clock duration* into units. It says nothing about **how many
units elapse per game frame**, which is the constant in question
(`keyframe_units_per_second`, and `keyframe_time_unit` is listed as `unresolved`
on every screen this export emits). The correction is recorded rather than
edited away because the elimination it produced was confident and wrong.
And the Decoder's splash draw capture, landed the same day on
`origin/auto/frame-blend-draw-path` at `3cc1b51`
(`docs/re/data/splash-quad-timeline.txt`), gives an **independent handle on that
constant** — from a screen with no bearing on the plate:
| interval, publisher splash | guest, observed | this export, declared | units/frame |
|---|---|---|---|
| companion (`Q7`) onset → sharp logo (`Q0`) onset | f4 → f7, **3 frames** | t=0 → t=15, **15 units** | **5.0** |
| companion onset → its own peak | f4 → f7 (a=240), **3 frames** | t=0 → t=15 (a=255), **15 units** | **5.0** |
| sharp logo onset → full alpha | f7 → f10, **3 frames** | t=15 → t=30, **15 units** | **5.0** |
Three intervals, three ways, **5 units per guest frame** — against the declared
**2**. At 30 fps that is ~150 units/s rather than 60, and the plate at t=236
would land at **1.57 s**, not 3.93 s. The port would be **~2.4 s late**, which
is the magnitude of a thing a human notices without a stopwatch.
⚠️ **Two caveats, and they are why this is asked rather than acted on.** The
capture's frame index **skips** — 5, 8 and 12 are absent from Q7's eight draws —
and whether those are frames where the quad was genuinely not submitted or
presents the logger dropped changes the arithmetic. And the present rate during
a splash is not established.
### 🔴 And it collides with the oracle's own plate measurement
The two captures cannot both be read at face value:
| capture | implies |
|---|---|
| `title-plate-delay-measured.md` — 118 units in 2.135 s | **~55 units/s** |
| `splash-quad-timeline.txt` — 15 units in 3 frames | **~150 units/s** at 30 fps |
A factor of **2.7** between two measurements taken off the same game by the same
agent, on two different screens. Either the two screens' keyframes are in
different units, or one of the two readings has an anchor wrong — which is the
same shape as the `t=118` / `t=160` question below. **Both are oracle
measurements and neither is the port's to resolve.** Asked in
[`BLOCKED.md`](BLOCKED.md) H3.
## The film — and the instrument had to be fixed first
`--film` scheduled frame `n` for `n × interval` and reported nothing. One
1280×720 `save_png` under llvmpipe costs ~0.24 s, so **a request for 0.05 s
delivered 247 frames in 60 s where 1 200 were asked for** — an achieved 4.1 fps
against a requested 20 — and the deficit accumulated silently into the frame
index. `f_071.png` still looked exactly like the frame that was meant to be
3.55 s in.
[`TEMPORAL-VERIFICATION.md`](../agents/TEMPORAL-VERIFICATION.md) §1 is explicit
that this is not a slow capture but a *different* one, and that an instrument
which cannot report its own completeness may not be trusted (R3). So `--film`
now writes `<film>_frames.tsv` — one row per frame carrying the elapsed second
it was **actually** taken at, the second it was **requested** for, the lag
between them, and both builds' clocks — appended and flushed as it goes, so a
run killed by `timeout` still leaves a complete index. It prints the achieved
rate against the requested one every 40 frames. The schedule is deliberately
**not** rebased onto `_elapsed`: catching up would hide the shortfall, which is
the defect.
The run this page rests on: requested 20 fps, **achieved 15.9 fps for the first
120 frames and 11.3 fps by frame 200**, lag reaching 8.7 s. Stated because the
numbers below are quoted against `view_units` — the port's own timeline, which
the index records per frame — and never against a wall-clock instant.
### Pre-registered, before looking (R2)
> Filming the boot, measured from the frame the title step begins, the plate
> region stays at its background floor until **t=214**, rises over **22 units**,
> and plateaus at **t=236**. Accept ±2 frames at each end.
### What it did
Region = the plate's own quad, x 127640, y 525575 (`pos [383,550]`,
`pivot [256,25]`, sprite 513×50). Mean of the crop, one row per filmed frame:
| `view_units` | region mean | |
|---|---|---|
| 7.8 → 143.8 | 0.13765, **flat to 5 decimals** | nothing there |
| 151.9 → 207.8 | 0.13773 → 0.14341 | a slow 4 % drift, build 4's own |
| **207.8 → 215.3** | 0.14341 → **0.15348** | ⬅ the step; brackets the declared **214** |
| 215.3 → 239.8 | → 0.21254 | the ramp |
| 239.8 → 279.3 | → 0.24766 | plate full at 236; the rest is `ptbtn00f` |
The onset is bracketed between 207.8 and 215.3 at a frame spacing of ~7.5 units,
i.e. **within one frame of the declared 214**, and `rest.t` was never touched.
Prediction held.
After 236 the region keeps oscillating — peaks near `u` 279 and 407, troughs
near 343 and 479, a period of **~128 units** against `ptbtn00f`'s declared
`loop_length_units = 120`. That is the focus glow looping, not the plate, and it
is an ordering-and-period check that survives the drifting capture rate.
## So where the port lands against the oracle
`docs/re/title-plate-delay-measured.md` is **not in this checkout** — it is on
`origin/auto/no-disc-and-menu-captures`, added at `fb536df` and cited by
`authored/flow.json` at `5b0a6e6`. Read there:
| | run 1 | run 2 | **the port** |
|---|---|---|---|
| settled → plate | 2.138 s | 2.132 s | **1.967 s** (118 units at 60/s) |
| first drawn → plate | 3.781 s | 4.263 s | **3.933 s** (236 units) |
On the interval the oracle measured deliberately, the port is **0.17 s early**.
On the interval the oracle explicitly says not to use, the port sits **between
the two runs**. No number anyone has taken makes this port's plate late.
**The human watched both and says late, and that observation stands.** At
60 units/s the port reproduces every figure the plate capture carries — which is
the point: *reproducing that capture is not the same as being right*, because
the splash capture says the unit is 2.5× off and would put the port 2.4 s late.
The two cannot both hold. The human's eye agrees with the splash capture.
## A second anchor question, also the Decoder's
**What does the oracle's "title settled" correspond to on the declared
timeline?** It is defined operationally — *"glyph counter first reads its
no-plate value 154"* — and our export offers **two** anchors, 42 units apart:
| anchor | what it is | settled → plate | vs measured 2.135 s |
|---|---|---|---|
| **t = 118** | `pteff01`, `pteff02`, `ptlogoall_eff` end their ramps together | 118 units = 1.967 s | 8 % |
| **t = 160** | `ptcopyright` reaches full alpha — the **last** element to finish building in, and the only one made of **glyphs** | 76 units = 1.267 s | 41 % |
The port's reconciliation picked 118. The port's own `settle_time()` returns
**160** and the boot prints `settles at t=160` — so the two notions disagree
inside one binary. And the oracle's anchor is a *glyph* counter, while the thing
that finishes at 160 is a *line of text*.
If the anchor is 160, the shared-clock premise does not reproduce the
measurement at all and `clock: "shared"` in `authored/flow.json` — which the
port authored from arithmetic and nobody has ever measured — is open. If it is
118, the reconciliation stands and H3 is not a timing defect.
**This cannot be settled here.** It is a question about what the Decoder's
instrument was looking at, and it is asked in [`BLOCKED.md`](BLOCKED.md) H3.
## Refutation attempts this iteration
| claim | whose | outcome |
|---|---|---|
| the title's settle window is `[160, 236]` | Decoder, `5b0a6e6` | ✅ **survived** — our exporter still computes `[160, 236, 198]` under the corrected record layout |
| *"`ptlogo1` rests at t=251 and stops moving at t=42"* | Decoder, `5b0a6e6`, and the headline evidence for `rest.t ≠ settle` | ❌ **refuted on its evidence** — in the current export `ptlogo1.rest.t` is **42**, equal to when it stops moving. The record-layout fix repaired exactly this element. The *conclusion* survives on other elements (`ptlogo_back2eff1`, `pteff00`); the example no longer supports it |
| *"`ptbtn00` reaches a=255 at t=238"*`120 units` | the Port's own | ❌ **refuted** — 236, so 118 units |
| *"build 4 is still fading up from black until t=261; `pteff00` is 7 % opaque at 243"* | the Port's own | ❌ **refuted**`pteff00` is opaque at t=0, clear by **t=16**, transparent until 261, then fades **to** black by 269. At t=243 it is 0 % opaque. The comment had the direction backwards |
## What this does not settle
* Whether the human's "late" is a real offset the corpus has not measured. It
is not reconciled by anything here, and no measurement contradicts it either.
* The `t=118` / `t=160` anchor. Asked, not guessed.
* Whether the plate **pulses** after arrival. The port holds it; the corpus
measures a ~2.24 s pulse. Filed already in `flow.json`'s `no_pulse_why`.
* Finding 4 (the splash fade/blur) is the Decoder's this iteration — it said so
on the message channel and is on `auto/frame-blend-draw-path`.
---
# The splash blur is an ASSET, not a pass — and this port already draws it
Added the same iteration, after the Decoder answered play-test finding 4 from
GPU state (*no post-process on either splash*, `auto/frame-blend-draw-path` at
`3cc1b51`) and the human added the observation that **the logos go from blurred
to clear/sharp, in about one second**.
Those two are not in tension. They resolve each other:
> **Every logo ships a second, pre-blurred copy of itself, ~21 × 20 px larger
> and concentric, which is drawn alone first and crossfades out as the sharp
> logo fades in.** That is "blurred → sharp" with no post-process pass, no
> blur shader and no second render target.
Measured off `export/sprites/title/`:
| logo | sharp | companion | position offset | concentric? |
|---|---|---|---|---|
| `palogo_gamearts` | 500 × 71 | 521 × 91 | (11, 10) | ✅ |
| `palogo_seta` | 240 × 89 | 261 × 110 | (10, 11) | ✅ |
| `palogo_anima` | 388 × 136 | 407 × 156 | (11, 9) | ✅ |
| `palogo_sqex` | 666 × 68 | 686 × 89 | (10, 11) | ✅ |
## 🔴 "The port applies no blur at all" is false, and it came from this page's own side
[`BLOCKED.md`](BLOCKED.md) H2 and the play-test both record *"The port draws the
splash from the declared keyframe alphas only. It applies **no blur at all**."*
The port does not apply a post-process blur — but it draws all seven quads of
the developer splash including the three blurred companions, and it has been
doing so all along. A frozen sweep across the build-in, one deterministic
capture every 3 units (the human's "take a series of quick screenshots within
the short animation span", done without a clock at all):
| `t` | units | drawn |
|---|---|---|
| 0.00 s | 0 | background only |
| 0.05 0.25 s | 3 15 | background + **the three blurred companions alone** |
| 0.30 0.70 s | 18 42 | **all seven** — the crossfade |
| 0.75 0.80 s | 45 48 | background + the three sharp logos |
Blurred first, then both, then sharp. The mechanism is reproduced. The claim
that it was not came from describing the renderer instead of running it.
## Cross-check of the Decoder's quad mapping — ✅ survived, independently
`splash-quad-timeline.txt` names its quads by NDC rectangle off the guest's
vertex stream. This export's declared rectangles, converted independently:
| | this export | the guest | agreement |
|---|---|---|---|
| `palogo_sqex` | x[0.517, +0.523] y[0.106, +0.083] | `Q0` x[0.520, +0.520] y[0.100, +0.080] | **~2 px** |
| `palogo_sqex_eff` | x[0.533, +0.539] y[0.133, +0.114] | `Q7` x[0.530, +0.540] y[0.130, +0.120] | **~4 px** |
Two decoders, two paths — a `.pak` read statically and a vertex stream logged
live — agreeing to a few pixels on both the sharp quad and the blurred one.
That is corroboration of the mapping and of `Q7` being the companion.
## What is still open on the splash
* **The extent.** The rectangles agree; the *alphas* are where the 5-units-per-
frame discrepancy above came from, and until that is settled it is not
possible to say whether the game holds the blurred copy longer than we do.
That is the same question as H3 and is asked once, there.
* Whether `palogo_eff0` (kind `0x10`, a full-screen quad, like the title's
`pteff00`) is the splash's fade veil. Not examined this iteration.

View File

@@ -83,10 +83,20 @@ var audio: MenuAudio = null
## 2 -- arrives later.
##
## **They share one clock, started together, and there is no authored delay.**
## The plate arrives at its own declared `t = 238`; build 4's visible build-in
## ends at `t = 118`; the 120-unit difference is 2.000 s, against an oracle that
## measured 2.138 s and 2.132 s at an emulator presenting 28.1 fps rather than
## 30. A port running at a true 30 Hz wants the declared 120, not the wall clock.
## The plate reaches full alpha at its own declared `t = 236`; build 4's effect
## quads end their ramps together at `t = 118`; the 118-unit difference is
## 1.967 s, against an oracle that measured 2.138 s and 2.132 s at an emulator
## presenting 28.1 fps rather than 30.
##
## 🔴 THIS SAID `t = 238` AND `120 units = 2.000 s`, AND BOTH ARE OFF BY TWO.
## `ptbtn00`'s declared alpha reaches `0xff` at **t = 236** and *holds* it to
## t = 238, then ramps back to 0 by t = 244 -- so 238 is the last opaque frame,
## not the arrival. `236 - 118 = 118`. The port has been printing the
## contradiction in one sentence on every boot: *"plate reaches full alpha at
## t=236 ... 120 units after build 4's last build-in ramp at t=118"*. The
## correction moves the reconciliation by 0.033 s and changes no conclusion,
## which is why it survived; a cited number that is wrong is still wrong.
## See `docs/port/plate-arrival-halves.md`.
##
## A second ScreenView rather than a second screen inside one, because that is
## what "two builds at once" actually is: each has its own timeline, its own
@@ -1053,14 +1063,59 @@ func _capture(path: String) -> void:
## A frame every 0.25 s for the whole run, so an unattended boot leaves a
## filmstrip behind rather than requiring someone to be watching it.
##
## 🔴 A FRAME INDEX IS NOT A CLOCK, AND THIS TOOL WAS BEING READ AS ONE.
##
## `_film_next += _film_interval` schedules frame `n` for `n * interval`. When a
## frame costs more than the interval -- and it does; one 1280x720 `save_png`
## under llvmpipe measured ~0.24 s, so any `--film-interval` under a quarter of
## a second is unreachable -- the deficit accumulates silently and every later
## frame is taken late by a growing amount. Asking for 0.05 s over 60 s
## requested 1 200 frames and produced **247**, an achieved 4.1 fps against a
## requested 20. Nothing in the output said so, and `f_071.png` still looks
## exactly like the frame that was meant to be 3.55 s in.
##
## `TEMPORAL-VERIFICATION.md` §1 is explicit that a capture that asked for one
## rate and delivered another "is not a slow capture, it is a **different**
## capture", and that an instrument which cannot report its own completeness may
## not be trusted (R3). So the film now carries its own timebase: one TSV row per
## frame with the elapsed second it was ACTUALLY taken at and both builds' clocks
## at that moment, appended as it goes so a run killed by `timeout` still leaves
## a complete index.
##
## The schedule is deliberately NOT rebased onto `_elapsed`. Catching up would
## hide the shortfall, which is the defect; falling behind and saying so is the
## fix. The achieved rate is printed every 40 frames and the row is the record.
func _film_capture() -> void:
var index_path := "%s_frames.tsv" % _film
var index := FileAccess.open(index_path, FileAccess.WRITE)
if index == null:
push_error("cannot write %s (%d)" % [index_path, FileAccess.get_open_error()])
return
index.store_line("frame\telapsed_s\trequested_s\tlag_s\tscreen\tview_units\toverlay_units")
print("film: %s, requested one frame every %.3f s; index -> %s"
% [_film, _film_interval, index_path])
while true:
await RenderingServer.frame_post_draw
if _elapsed >= _film_next:
var at := _elapsed
var img := viewport.get_texture().get_image()
img.save_png("%s_%03d.png" % [_film, _film_frame])
# The overlay's clock, or -1 where there is no second build. A blank
# would read as zero, and zero is a real instant on that timeline.
var over := -1.0
if overlay != null:
over = overlay.time_units
index.store_line("%d\t%.4f\t%.4f\t%.4f\t%s\t%.3f\t%.3f" % [
_film_frame, at, _film_next, at - _film_next,
String(view.screen.get("name", "?")) if view != null else "?",
view.time_units if view != null else -1.0, over])
index.flush()
_film_frame += 1
_film_next += _film_interval
if _film_frame % 40 == 0 and at > 0.0:
print("film: %d frames in %.2f s -- achieved %.2f fps against a requested %.2f"
% [_film_frame, at, _film_frame / at, 1.0 / _film_interval])
# Godot passes everything after `--` through untouched; take `--key=value`.
@@ -1447,16 +1502,27 @@ func _raise_overlay(name: String) -> void:
# no log line.
if _sequence.is_empty():
return
# %d - 118, computed rather than spelled: the literal "120" here disagreed
# with the "t=236" printed beside it in the same sentence, for weeks.
print(" plate reaches full alpha at t=%d (%.2f s on the shared clock), \
120 units after build 4's last build-in ramp at t=118"
% [int(overlay.settle_time()), visible_at])
%d units after build 4's effect quads end together at t=118"
% [int(overlay.settle_time()), visible_at, int(overlay.settle_time()) - 118])
if not _play and _film == "":
# The LATER of the two, not the overlay's alone. The plate arrives at
# t=238 and build 4 is still fading up from black until t=261 -- its
# `pteff00` quad is 7 % opaque at 243 -- so quitting when the plate
# lands photographs a title that has not finished presenting. The first
# capture taken this way was visibly darker than the one before it, and
# nothing in the log said why.
# The LATER of the two, not the overlay's alone.
#
# 🔴 THE REASON GIVEN HERE WAS BACKWARDS AND IS REPLACED. It said build 4
# "is still fading up from black until t=261 -- its `pteff00` quad is
# 7 % opaque at 243". `pteff00` is the screen's black veil and it does
# BOTH fades: opaque at t=0, clear by **t=16**, transparent all the way
# to t=261, then back to opaque by t=269. At t=243 it is 0 % opaque, not
# 7 %, and 261..269 is the fade-OUT. So build 4 finishes arriving 16
# units in, not 261.
#
# The line still stands, for the reason underneath it rather than the
# one that was written: build 4's own last hold is t=160 (`ptcopyright`
# reaching full alpha) and the plate's is t=236, so ending on the
# overlay alone would still be ending on the earlier of two clocks. The
# darker capture that prompted this was real; the explanation was not.
var ends_at := maxf(view.settle_time(), overlay.settle_time()) / view.units_per_second
_overlay_quit_at = _elapsed - (view.time_units / view.units_per_second) + ends_at
print(" boot ends at %.2f s, once both builds have arrived (t=%d)"

View File

@@ -36,6 +36,35 @@ step() { # name, expectation, command...
esac
}
# 🔴 GODOT'S SCRIPT CLASS LIST IS A BUILD CACHE, AND IT IS GITIGNORED.
#
# `port/.godot/global_script_class_cache.cfg` is what resolves a `class_name`,
# and `.gitignore` excludes `port/.godot/` -- correctly, it is derived. So a
# checkout that MERGES a commit adding a new `class_name` keeps a cache that
# does not list it, and every script referencing the new class fails to parse:
#
# SCRIPT ERROR: Parse Error: Identifier "Gamepad" not declared in the current scope.
# ERROR: Failed to load script "res://scripts/boot.gd" with error "Parse error".
#
# The whole project then refuses to load, from `--screen` to `--boot`, and the
# error names the symbol rather than the cache -- so it reads as a missing file
# or a bad merge. This is exactly what merging the human's input fix did on
# 2026-09-01: `gamepad.gd` arrived with `class_name Gamepad`, the cache in this
# container was warm and predated it, and the port did not run at all.
#
# A fresh clone has no `.godot/` and Godot builds one on first run, so nobody
# hits this until they merge into a working tree -- which is every iteration of
# this loop. Reimporting is cheap and idempotent, so it runs unconditionally
# rather than behind a staleness test that would itself need to be right.
echo "godot: reimporting so class_name resolves against a fresh cache"
DISPLAY="$DISPLAY" godot --headless --path port --import >"$OUT/godot-import.log" 2>&1 \
|| { echo " 🔴 godot --import FAILED -- see $OUT/godot-import.log"; fail=1; }
for c in $(grep -ho '^class_name [A-Za-z_][A-Za-z0-9_]*' port/scripts/*.gd | awk '{print $2}'); do
grep -q "\"$c\"" port/.godot/global_script_class_cache.cfg 2>/dev/null \
|| { printf ' %-24s 🔴 class_name %s is not in the class cache\n' class-cache "$c"; fail=1; }
done
echo
echo "asserting checks:"
step format-validator must-pass "$BIN" check
# The contract lives on a branch this checkout does not merge: HANDOFF on `main`