diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index f0fa3d74..25743535 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -547,3 +547,31 @@ declared instants and holds it. What a capture would still settle is whether the real plate **pulses** after that, and whether `ptbtn00f.rat` — the focus record, which the port now draws not at all — is a glow layered *over* the plate. Drawing both would be a rendering rule nobody has measured. + +--- + +## The `ptloop` sweep phase: ~400 units fits the capture, the refined fit says 357.7 + +*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port.* + +`--leaf-time=` now poses the sweeps' leaf independently of the screen, so the +Decoder's refined sweep fit is testable for the first time — it never was, because +`verify-capture` passed it as a whole-screen `--time` that `pose_at` discarded. + +Against `live-title-build4-no-plate.png`, structural disagreement bottoms out in a +sharp basin at **390–415 units (0.0124 %)** and reads **0.2532 % at t=357.7** — a +20× separation, on a deterministic renderer, with the sweeps demonstrably moving +0.40 % of the frame between phases. + +**Nothing in the port changes and nothing is being asked for urgently.** The port +loops the leaf freely; there is no phase to set, and re-posing the harness to the +fitted value would be tuning until they match. + +What would be useful: **whether 357.7 and this are the same quantity.** If the +refined fit was measured against this same capture, one of the two is off by ~42 +units and it is worth knowing which. If it came from a different frame, then ~400 +is a second, independent phase measurement and neither is wrong. + +⚠️ The port cannot separate a phase error from a systematic error in how it draws +the sweeps — a geometry mistake could be absorbed by shifting the phase. The +sharpness of the basin argues against that, but one capture cannot settle it. diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 0a18f9a7..55e548bc 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -5920,3 +5920,63 @@ iteration's two silent ones. `--pose=rest` (max 111 against the timeline), `--play` (enters the menu), `--no-hold` (max 255 on two screens) all pass. `--time` was the one that did not, and it took a screen whose content is a single late spike to make the failure visible. + +## The title's residual is the sweep phase, and the sweeps fit at ~400 units, not 357.7 + +Last iteration I found `verify-capture` was passing the Decoder's refined sweep +fit as `--time=5.9617` and having it silently discarded, so the value had **never +been tested**. Asking for it also destroyed the frame — t=357.7 is past the +title's own group end at t=269 — which is why nobody noticed. + +`--leaf-time=` separates the two clocks: the screen sits at its settled +pose, the `ptloop` leaf is placed at whatever phase is under test. That makes the +fit testable for the first time. + +### Controls first + +* The renderer is **deterministic** — three runs at one leaf phase are + bit-identical, max difference 0. So variation across phases is signal, not noise. +* The sweeps are **detectable** — two phases differ over **0.3953 %** of the frame + at a 10 % threshold. A comparison at this scale can see them. + +### The fit + +Sweeping the leaf across its full 600-unit span against +`live-title-build4-no-plate.png`, structural disagreement at a 25 % threshold: + +| leaf phase | differing | +|---|---| +| 240 units | 0.1410 % | +| 320 units | 0.3395 % | +| **357.7 — the Decoder's fit** | **0.2532 %** | +| 390 | 0.0129 % | +| **395–402** | **0.0124 %** | +| 405 | 0.0127 % | +| 440 | 0.2033 % | + +A sharp basin at **390–415 units**, an order of magnitude below everything +around it, and **20× better than t=357.7**. + +### What that means, and what it does not + +✅ **The title's 0.21 % residual is the sweep phase, not structure.** At the +fitted phase the disagreement falls to **0.0124 %** — the same order as the +splashes' 0.01 %. The port's title rendering is structurally right; the sweeps +were simply somewhere else in their loop. + +🔴 **The port does not adopt 400 units, and `verify-capture` is not re-posed to +it.** That would be tuning until they match, which this repository's own tooling +header warns against. The port loops the leaf freely — there is no phase +parameter to set — and 400 units is a property of *that capture's instant*, not +of the game. + +⚠️ Reach: this assumes the port's leaf geometry and sprite are otherwise correct. +A systematic error in how the sweeps are drawn could be absorbed by shifting the +phase, and one capture cannot separate those. What makes the result worth having +anyway is the **sharpness** — a 20× drop over 40 units is not something a +geometry error would produce at a wrong phase. + +⚠️ And it does not tell the Decoder their 357.7 is wrong *as a measurement of +whatever they measured it on*. It says the phase that matches this capture is +~400. If those are the same quantity, one of the two is off by ~42 units; if they +are not, this is a second quantity nobody had. diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index af167851..4549449d 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -239,6 +239,12 @@ func _ready() -> void: name, view.screen["elements"].size(), view.screen["paint_order"].size(), design[0], design[1], settle, settle / view.units_per_second]) + # `--leaf-time=` places the LEAF alone, leaving the screen settled. + # See `ScreenView.leaf_time_units`. + if args.has("leaf-time"): + view.leaf_time_units = float(args["leaf-time"]) * view.units_per_second + view.queue_redraw() + if args.has("time"): _frozen = true # An explicit instant beats the settle instant -- see `ScreenView.frozen`. diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index 2419a26e..a6587cd7 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -113,6 +113,19 @@ var draw_leaf_for: Array = [] ## Whether this screen replays a leaf's group. See `authored/rendering.json`. var loop_leaf := false +## Pin the LEAF's phase independently of the screen's pose, in units. -1 = off. +## +## Built because a measured value could not be tested. The Decoder's refined fit +## for the `ptloop` sweeps is t=357.7 units, and `verify-capture` passed it as +## `--time=5.9617` -- which poses the WHOLE SCREEN there. The title's own group +## ends at t=269, so that fades everything out and scores 30.97 % against the +## capture. The instant was only ever about the sweeps, whose leaf runs to t=600. +## +## So the fit was untestable: the only way to ask for it also destroyed the rest +## of the frame. This separates the two clocks -- the screen sits at its settled +## pose, the leaf is placed at whatever phase is being tested. +var leaf_time_units: float = -1.0 + ## While true the screen holds at `rest` and never plays its exit. The ## sequencer clears it to send the screen away. var holding: bool = true @@ -509,14 +522,14 @@ func _draw_leaf(element: Dictionary) -> bool: # cannot tell "loops at 600" from "runs once for 600 and stops". var was := holding holding = false - var t := time_units + var t := leaf_time_units if leaf_time_units >= 0.0 else time_units if loop_leaf: var span := 0.0 for k: Dictionary in fe.get("keyframes", []): if k.has("t"): span = maxf(span, float(k["t"])) if span > 0.0: - t = fposmod(time_units, span) + t = fposmod(t, span) var pose := pose_at(fe, t) holding = was # 🔴 A SCALE-0 LEAF MUST NOT CLAIM THE DRAW. The Decoder hit this in its own diff --git a/tools/port/verify-capture b/tools/port/verify-capture index 16fdea6d..73f3b380 100755 --- a/tools/port/verify-capture +++ b/tools/port/verify-capture @@ -135,7 +135,7 @@ for row in "${MAP[@]}"; do note="" case "$name" in main_menu|extras) note="rendered with authored initial focus" ;; - title) note="posed at the SETTLE (t=198); see the note in this file" ;; + title) note="settle t=198; residual is the SWEEP PHASE -- see DECISIONS.md" ;; esac # Where the difference lives. This comes FIRST because it is what the gamma # sweep has to be protected from.