port: F6 FOUND -- the gate was declared data the renderer was discarding

The sweep starts early because ScreenView drew a leaf WITHOUT multiplying its
parent's alpha in. ptloop01/ptloop02 declare 0:0 70:0 100:255 238:255 250:0, so
the sweep is invisible until t=70 and full at t=100. The port has had that ramp
in its export the whole time and was throwing it away at the draw call.

Three changes:

1. Parent alpha multiplies into the leaf's. This file asserted the opposite,
   arguing from the sweeps being drawn while their parent had expired -- but that
   could not separate 'the leaf wins' from 'the parent is ignored because it
   draws nothing', and its own comment said so and named the interval that would
   settle it. Measured there: dividing the leaf's declared curve out of the drawn
   alpha pins the implied parent at 255.0 (+/-1.5) over hundreds of frames while
   the drawn alpha swings 242 -> 132 -> 145.

2. rate 0.5, calibrated against a DECLARED interval (ptloop01's own 30-unit ramp)
   rather than captured frames: two runs differing 2x in frames give 0.4795 and
   0.4667, 2.7% apart. This number was 0.514, withdrawn, now reinstated at 0.5 --
   what failed the first time was quoting a rate in presents, and the ratio was
   discarded along with the frames.

3. The 107-unit authored offset is DELETED, with its guard tool. It came from a
   ratio measured against an element identified only by screen position; the
   declared ramp needs no such identification. Deleting an authored value because
   the data already says it is the outcome PORT-MISSION asks for.

Also reverted, same day I made it: removing the transparent-parent skip. I read
'the game submits the sweep ~950 frames past its parent's expiry' as the leaf
outliving the parent. It does not follow -- a draw submitted at alpha 0 is still
a draw, and submitted is not visible. With the multiply, skipping on a
transparent parent IS multiplying by zero.

VERIFIED, pre-registered before running, via --probe-leaf:
  u= 60 -> no draw at all           (predicted: parent alpha 0.00, gated off)
  u=100 -> leaf_t 50.0,  x -439     (predicted 50.0, -439)
  u=236 -> leaf_t 118.0, x -167     (predicted 118.0, -167)
verify-capture: every row unchanged.

⚠️ BUILD-SENSITIVE: builds 5 and 6 of GP_TITLE declare these records as a single
flat a=255 with no ramp. This export reads the ramped build; if that changes the
gate disappears silently.
This commit is contained in:
Sylpheed port agent
2026-09-02 19:47:20 +00:00
parent af10a2ec3c
commit ac1371cda3
4 changed files with 56 additions and 104 deletions

View File

@@ -150,8 +150,6 @@ step decisions-index must-pass tools/port/index-decisions --check
# is merely on a peer's unmerged branch is reported, because the fix is a merge
# and nobody in this container can make it.
step trajectory-fit must-pass tools/port/fit-trajectory --selftest
step leaf-onset must-pass tools/port/check-leaf-onset
step leaf-onset-ctl must-pass tools/port/check-leaf-onset --selftest
step doc-citations must-pass tools/port/check-citations
step citations-control must-pass tools/port/check-citations --selftest
# A refuted claim asserted outside its correction is a lie the corpus tells a

View File

@@ -1,59 +0,0 @@
#!/usr/bin/env python3
"""Is `leaf_clock.start_units` still the measured FRACTION of its anchors?
tools/port/check-leaf-onset [--selftest]
The measurement behind F6's sweep onset is a RATIO -- 0.500 of the interval from
the title's first visible element to the plate's onset -- because every
unit-valued figure from the captures was withdrawn (frames are presents; the
unit conversion carried a 3.2x clock conflict). A ratio needs no clock.
`authored/rendering.json` stores the resolved `start_units` so the runtime does
no arithmetic. That resolution is only valid against the keyframes it was
computed from, and a re-export that re-times either anchor moves them silently.
This recomputes it. It is a staleness guard, not a measurement.
"""
import json, sys
TOL = 1.0
def anchors():
t = json.load(open("export/screens/title/title.json"))
start = None
for el in t["elements"]:
for k in el.get("keyframes", []):
if (int(k["fade_argb"], 16) >> 24) > 0:
start = k["t"] if start is None else min(start, k["t"])
break
p = json.load(open("export/screens/title/press_start.json"))
ks = [(k["t"], int(k["fade_argb"], 16) >> 24) for k in p["elements"][0]["keyframes"]]
onset = next(t0 for (t0, a0), (_, a1) in zip(ks, ks[1:]) if a0 == 0 and a1 > 0)
return float(start), float(onset)
def main():
if "--selftest" in sys.argv:
lo, hi = anchors()
good = lo + 0.5 * (hi - lo)
ok_pass = abs(good - (lo + 0.5 * (hi - lo))) <= TOL
ok_fail = abs((good + 25.0) - (lo + 0.5 * (hi - lo))) > TOL
print("selftest: correct value accepted=%s, value off by 25 rejected=%s -> %s"
% (ok_pass, ok_fail, "ok" if (ok_pass and ok_fail) else "🔴 BROKEN"))
return 0 if (ok_pass and ok_fail) else 2
cfg = json.load(open("authored/rendering.json"))["leaf_clock"]["title"]
lo, hi = anchors()
want = lo + float(cfg["start_fraction"]) * (hi - lo)
have = float(cfg["start_units"])
print("anchors: title first visible t=%.0f, plate onset t=%.0f, span %.0f" % (lo, hi, hi - lo))
print("fraction %.3f -> %.1f units; authored start_units %.1f" % (cfg["start_fraction"], want, have))
if abs(want - have) > TOL:
print("🔴 start_units is STALE by %.1f units -- the anchors moved under it." % (have - want))
return 1
print("resolved onset still matches its measured fraction")
return 0
if __name__ == "__main__":
raise SystemExit(main())