diff --git a/authored/rendering.json b/authored/rendering.json index 9f3b449d..3b5df0fe 100644 --- a/authored/rendering.json +++ b/authored/rendering.json @@ -171,41 +171,40 @@ ], "leaf_clock": { "title": { - "start_units": 107.0, - "rate": null, - "start_fraction": 0.5, - "fraction_from": "title first visible element (t=0)", - "fraction_to": "press_start ptbtn00 onset (t=214)" + "start_units": null, + "rate": 0.5 } }, "leaf_clock_why": [ - "leaf_t = screen_t - start_units. rate is null = the leaf runs at the screen's", - "rate; nobody has a clock-free measurement of a rate and the one attempt was", - "withdrawn (below).", + "leaf_t = rate * screen_t. A null field means 'not measured'.", "", - "start_units 107 = 0.500 x (214 - 0), resolved HERE from this export's own", - "declared keyframes. The 0.500 is the measurement; the 107 is arithmetic on it.", + "rate 0.5 -- the leaf's clock runs at HALF the screen's.", + " Measured by the Decoder against a DECLARED calibration rather than captured", + " frame counts: using ptloop01's own declared 30-unit alpha ramp (t=70..100) as", + " an in-capture title clock, two runs whose frame counts differ 2x give", + " leaf/title = 0.4795 and 0.4667, 2.7% apart.", + " docs/re/f6-unit10-parent-alpha-gates-the-sweep.md.", "", - "MEASURED as a RATIO, which is the point: (onset - title start) / (plate - title", - "start) = 0.489 and 0.507 across two independent captures, 3.7% apart", - "(Decoder, docs/re/f6-unit9-sweep-period-and-onset.md + 15670f4). A ratio needs", - "no clock, which is why it survived when every unit-valued number did not.", + " \ud83d\udd34 This number was adopted as 0.514, withdrawn, and reinstated as 0.5 within", + " one day. What failed the first time was quoting a rate in captured FRAMES --", + " frames are presents and the present rate differs per run (1168 vs 600 for one", + " animation). The ratio itself was sound; it was discarded with the frames.", + " A calibration that is itself declared does not have that failure mode.", "", - "\ud83d\udd34 EVERY TITLE-UNIT FIGURE FROM THE CAPTURES IS WITHDRAWN, twice over:", - " - rate 0.514 units/frame: captured frames are PRESENTS and the present rate", - " differs per run -- 1168 frames vs 600 for the same animation, 1.947x.", - " - offset '+40 units': converted through the plate's declared 12-unit ramp,", - " a calibration that puts title-start->plate at 75 units where the declared", - " data puts the plate at 238. That is a 3.2x clock conflict, still open, and", - " it is F4's `clock: shared` question.", - "Both were adopted here and both were reverted before shipping.", + "start_units null -- there is NO authored onset any more, and that is the point.", + " F6's gate is DECLARED: ptloop01/ptloop02 carry `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 had", + " that ramp in its export the whole time and was discarding it, because", + " ScreenView drew the leaf without multiplying the parent's alpha in.", "", - "\ud83d\udccc The human said the animation LOOKS correct while we were carrying a rate", - "that would have made it twice too fast. That was evidence against the number", - "and both agents filed it as a puzzle. An impression is weak about magnitudes", - "and strong about gross wrongness; this one was being read as the former.", + " A 107-unit offset was authored here earlier, from a measured 0.50 ratio. It", + " is retired: the ratio was measured against an element identified only by", + " screen position, and the declared ramp needs no such identification. Deleting", + " an authored value because the data already says it is the better outcome.", "", - "\u26a0\ufe0f 107 is arithmetic on THIS export's keyframes. tools/port/check-leaf-onset", - "recomputes it and fails if a re-timing moves the anchors underneath it." + "\u26a0\ufe0f BUILD-SENSITIVE. Builds 5 and 6 of GP_TITLE declare these same two records", + "as a single flat a=255 keyframe -- no ramp, hence no gate. This export reads", + "the ramped build (verified: 0:0 70:0 100:255 238:255 250:0). If that ever", + "changes the gate silently disappears." ] } diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index d65afedc..f37718a1 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -708,7 +708,7 @@ static func _rot_of(pose: Dictionary) -> float: ## "the parent is ignored because it draws nothing" are NOT separated. A capture ## during t=100...238 would separate them. ## Returns whether anything was actually drawn, so the caller can fall back. -func _draw_leaf(element: Dictionary) -> bool: +func _draw_leaf(element: Dictionary, colour: Color) -> bool: var any_drawn := false for fe: Dictionary in element.get("leaf", {}).get("elements", []): var rel: String = fe.get("sprite", "") @@ -758,7 +758,24 @@ func _draw_leaf(element: Dictionary) -> bool: skipped.append("%s (leaf scale 0 -- parent drawn instead)" % fe.get("id", "")) continue var pivot := _vec(fe.get("pivot", [0, 0])) - _draw_quad(tex, placement(pose, pivot, tex.get_size()), modulate_of(pose), + # 🔴 THE PARENT'S ALPHA MULTIPLIES IN, and this file said the opposite + # until 2026-09-02. The old text argued from the sweeps being drawn at + # t=355 while their parent had expired -- but that reasoning 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. + # + # Measured there: dividing the leaf's declared curve out of the drawn + # alpha pins the implied parent at 255.0 (+/-1.5) across hundreds of + # frames while the drawn alpha swings 242 -> 132 -> 145 + # (`docs/re/f6-unit10-parent-alpha-gates-the-sweep.md`). + # + # 📌 And this IS F6's gate, from declared data rather than a measured + # constant: `ptloop01`/`ptloop02` declare `0:0 70:0 100:255`, so the sweep + # is invisible until t=70 and full at t=100. The port had the ramp in its + # export the whole time and was throwing it away at this line. + var leaf_colour := modulate_of(pose) + leaf_colour.a *= colour.a + _draw_quad(tex, placement(pose, pivot, tex.get_size()), leaf_colour, pivot, _vec(pose.get("pos", [0, 0])), _rot_of(pose)) drawn.append(fe.get("id", "")) any_drawn = true @@ -915,19 +932,16 @@ func _draw() -> void: var pose: Dictionary = element.get("rest", {}) if pose_mode == Pose.REST \ else pose_at(element, time_units) var colour := modulate_of(pose) - # 🔴 A LEAF OUTLIVES ITS PARENT, AND SKIPPING HERE KILLED IT EARLY. + # 🔴 I REMOVED THIS GUARD ON 2026-09-02 AND PUT IT BACK THE SAME DAY. # - # The parent's pose gates the whole element, so `ptloop01` expiring at - # t=250 stopped the sweep being drawn at all -- while this file's own - # decode says the leaf runs on its OWN timeline and the parent's alpha is - # not multiplied in. The two were inconsistent and the gate won silently. - # - # The capture settles it: the game submits the sweep across frames - # 746..1913 while the parent expires at frame 956, so it keeps drawing for - # roughly 950 frames after the parent is gone - # (`f6-unit3-sweep-track-fits.md`, `f6-unit8-leaf-clock-in-title-units.md`). - # A transparent parent is not evidence that its leaf is transparent. - if colour.a <= 0.0 and not draw_leaf_for.has(id): + # The argument for removing it was that the game keeps SUBMITTING the + # sweep for ~950 frames after its parent expires, which is true and which + # I read as "the leaf outlives the parent". It does not follow: a draw + # submitted with alpha 0 is still a draw in the command stream, and + # submitted is not visible. Now that the parent's alpha is known to + # multiply into the leaf's (below), skipping on a transparent parent is + # exactly multiplying by zero, and the two are pixel-identical. + if colour.a <= 0.0: # 🔴 THIS LINE USED TO SAY "at rest" WHATEVER INSTANT IT HAD POSED. # # On the timeline path the pose is `pose_at(time_units)`, not @@ -953,7 +967,7 @@ func _draw() -> void: # and rotation. See `_draw_leaf`. if element.get("leaf_carries_geometry", false) \ and draw_leaf_for.has(String(element.get("id", ""))) \ - and _draw_leaf(element): + and _draw_leaf(element, colour): continue # A FOCUSED button draws its record INSTEAD of its base sprite -- measured, # the focused sprite covers the base at 100.0 % of base-visible pixels. diff --git a/tools/port/check-all b/tools/port/check-all index 7e21209c..007bb095 100755 --- a/tools/port/check-all +++ b/tools/port/check-all @@ -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 diff --git a/tools/port/check-leaf-onset b/tools/port/check-leaf-onset deleted file mode 100755 index 55ca5e82..00000000 --- a/tools/port/check-leaf-onset +++ /dev/null @@ -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())