diff --git a/authored/rendering.json b/authored/rendering.json index 4e783040..9f3b449d 100644 --- a/authored/rendering.json +++ b/authored/rendering.json @@ -171,38 +171,41 @@ ], "leaf_clock": { "title": { - "start_units": 40.0, - "rate": null + "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)" } }, "leaf_clock_why": [ - "leaf_t = rate * (screen_t - start_units); a null field means 'not measured',", - "and rate null is the identity, i.e. the leaf runs at the screen's rate.", + "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).", "", - "\ud83d\udd34 RATE WITHDRAWN 2026-09-02, the same day it was adopted. It was 0.514, from", - "the Decoder's f6-unit8. They withdrew it: captured FRAMES are presents, and the", - "present rate differs per run -- the same animation took 1168 frames in one", - "capture and 600 in another, 1.947x apart, with the run's own baseline moving", - "1.953x alongside. So 0.514 measured that capture's pacing, not the game.", + "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.", "", - "\ud83d\udccc The tell was visible BEFORE the withdrawal and neither of us weighed it", - "properly: the human, watching both, said the animation itself looks correct.", - "A port running 1.0x against a real 0.514x would be twice too fast and they", - "would have said so. Their 'looks right' was evidence against the number and it", - "was recorded as a puzzle instead of as a measurement. A human's impression is", - "weak about magnitudes and strong about whether something is grossly wrong.", + "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\udfe1 start_units 40 is KEPT but is not settled. Two independent captures agree in", - "RATIO form -- the sweep begins 0.798 and 0.791 'baselines' before the plate", - "appears, 0.9% apart -- and ratios need no clock, which is what makes them", - "survive the withdrawal above. Converting that ratio into title units needs the", - "title's own rate, and that is \ud83d\udfe1: in one capture ptcopyright's 22-unit ramp", - "reads 1.0 units/frame while the plate's 12-unit ramp reads 0.571, a factor of", - "1.75. Until that is resolved the offset is directionally right and numerically", - "provisional.", + "\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.", "", - "Kept rather than reverted because the port's alternative is not a measurement", - "either: 0 offset is the unexamined assumption that the leaf shares the screen's", - "clock, and every measurement so far says it does not." + "\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.", + "", + "\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." ] } diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index 3b31a37d..70037e1b 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -366,6 +366,9 @@ func _ready() -> void: view.leaf_time_units = float(args["leaf-time"]) * view.units_per_second view.queue_redraw() + if args.has("probe-leaf"): + view._probe_leaf = true + 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 7839e017..d65afedc 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -201,6 +201,11 @@ var leaf_time_units: float = -1.0 ## ## `leaf_time_units` above is a different thing -- an ABSOLUTE override for the ## `--leaf-time` diagnostic, which pins a pose and ignores both of these. +## `--probe-leaf` only: print each leaf's clock and pose as it is drawn. A +## diagnostic, because "the authored file says 107" and "the renderer applies +## 107" are different claims and this port has confused them once already. +var _probe_leaf := false + var leaf_start_units: float = -1.0 var leaf_rate: float = -1.0 @@ -213,9 +218,20 @@ var leaf_rate: float = -1.0 ## start is visually indistinguishable from not drawing, without disturbing the ## parent-fallback path that `_draw_leaf` returns into. func leaf_clock(screen_units: float) -> float: - if leaf_start_units < 0.0 or leaf_rate <= 0.0: + # 🔴 THE TWO ARE INDEPENDENT, and an earlier version required BOTH. It read + # `if start < 0.0 OR rate <= 0.0: return screen_units`, so when the measured + # rate was withdrawn and set back to null, the ADOPTED OFFSET stopped being + # applied too -- silently, with the authored file still stating it. It was + # caught only because the offset was re-verified by probing the renderer + # rather than by re-reading the data that had just been edited. + if leaf_start_units < 0.0 and leaf_rate <= 0.0: return screen_units - return maxf(0.0, (screen_units - leaf_start_units) * leaf_rate) + var t := screen_units + if leaf_start_units >= 0.0: + t -= leaf_start_units + if leaf_rate > 0.0: + t *= leaf_rate + return maxf(0.0, t) ## While true the screen holds at `rest` and never plays its exit. The ## sequencer clears it to send the screen away. @@ -723,6 +739,8 @@ func _draw_leaf(element: Dictionary) -> bool: if span > 0.0: t = fposmod(t, span) var pose := pose_at(fe, t) + if _probe_leaf: + print("PROBE u=%.1f leaf_t=%.1f x=%s" % [time_units, t, pose.get("pos", [])]) holding = was # 🔴 A SCALE-0 LEAF MUST NOT CLAIM THE DRAW. The Decoder hit this in its own # renderer: its leaf branch marked the element drawn unconditionally, but diff --git a/tools/port/check-all b/tools/port/check-all index 007bb095..7e21209c 100755 --- a/tools/port/check-all +++ b/tools/port/check-all @@ -150,6 +150,8 @@ 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 new file mode 100755 index 00000000..55ca5e82 --- /dev/null +++ b/tools/port/check-leaf-onset @@ -0,0 +1,59 @@ +#!/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())