diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 92250902..78ee6b4e 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -105,7 +105,7 @@ method finding that applies to the Decoder's harness as much as this one: | # | ask | why the port cannot answer it | |---|---|---| | **H1** | 🟢 **PARTLY ANSWERED 2026-09-01 and the port has adopted the half that landed.** The game **digitises the left stick to four direction bits at 61 % deflection** and never sees a velocity (`input-button-numbering-is-remapped.md`). `Gamepad.ENTER` moves **0.5 → 0.61**: 0.5 was a *floor* (Godot's `ui_*` action deadzone), 0.61 is the game's own threshold, and between them Godot reports a direction the real game does not. Asserted at the device level in `tools/port/verify-input`. **Still open: does a held direction REPEAT, and at what rate** — one step per deflection remains authored, and the digitise-to-bits mechanism corroborates that a rate cannot come from this layer. | 🔴 **The 0.11 hysteresis gap stays AUTHORED** — nothing measured says the game has hysteresis at all. ⚠️ **A human chose 0.5 and 0.61 changes how the stick feels**; revert that one constant if it reads as needing too much push. ✅ And I never consumed the mislabelled pad bit table — checked by grep over `port/`, `authored/` and `tools/port/`, not remembered. | -| **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=3–15, all seven at u=18–42, 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). | +| **H2** | 🟡 **HALF-ANSWERED, AND I MARKED IT ✅ ON EVIDENCE THAT COULD NOT BEAR IT.** The **mechanism** half stands and is the Decoder's: no post-process pass, the blur is a baked companion texture ~21×20 px larger, crossfaded out as the sharp logo fades in. Decoded, correct, unchanged. | 🔴 **The BEHAVIOUR half was false and this row asserted it.** I wrote *"the port draws all seven quads — verified by a frozen sweep, 3 units a step"*. True, and it did not mean what I used it for: **a frozen sweep drives the clock by hand.** It proves the renderer CAN draw pose N; it says nothing about whether the poses are drawn in sequence while running. They were not — `pose_at` **assigned** the settle instant instead of clamping to it, so every element sat at its settled pose from a screen's first frame and no build-in was ever drawn. A human on a 140 fps GPU saw it in one boot: *"the logos just switch."* ⚠️ **All three of my instruments passed it** — frozen sweep, a 0.01 % settled comparison (a frozen screen matches a settled reference *perfectly*), and an achieved-fps counter (identical pixels 25×/s score like animating). Every one measured throughput or a pose; **none measured CHANGE**, which is the same shape as `InputEventAction` bypassing the input map. Fixed and gated by a film: build-in motion 40 % → 86 %, distinct luma states 120 → 152, splash motion 12 % → 24.8 % against the game's 21.2/27.8 %, with the 3.30 s hold unchanged because the Decoder measured the game holding 3.34 s. `tools/port/verify-motion` is now in `check-all` and was run against the reverted defect to confirm it fails on it. | | **H3** | ✅ **CLOSED 2026-09-01. ALL FOUR NAMED CAUSES ARE DEAD AND THE HUMAN'S OBSERVATION IS NOT — recorded that way deliberately rather than left green.** The rate is **56.8 units per guest second**, measured, control at 1.15 %, two elements agreeing at one clock (`units-per-second-measured.md`). 30 and 120 both excluded. At 56.8 the plate's t=236 lands at **4.15 s** against the port's 3.93 s — the port is fractionally **early**. | Eliminated in order: `rest.t` (the arrival is a declared keyframe), the clock origin (85/85 filmed frames share one clock), the anchor (answered t=160, and `clock: "shared"` survives it), the unit constant (56.8). 🔴 **`units = 2 × frames` is dead as a route** — the same animation takes 21 labels in one capture and 33 in another. ✅ Audited: this port never used it; `boot.gd` integrates `delta * units_per_second`, so the retirement cost a *justification* in `authored/timing.json` and not a behaviour, and that file's second leg (12 declared units against a 0.14–0.30 s black plateau = 40–86 units/s) has no frames in the chain. **60 units/s is KEPT** — 56.8 is 5.6 % away against ~5 % quantisation and the Decoder did not ask for a move. ⚠️ Reach is the **title**; the splashes are a different `GamePart`. What the human saw is now unattributed — see [`plate-arrival-halves.md`](plate-arrival-halves.md) for the two remaining candidates, of which the strongest is that **Ⓐ was unbound on the play-test build so that human could not skip the 137 s intro**, and the run they judged is not the run any of these measurements describes. | 🟡 **A LIVE CANDIDATE FOR FINDING 4, ADDED 2026-09-01, AND IT IS THE PORT'S NOT diff --git a/tools/port/check-all b/tools/port/check-all index b7aec672..1c883ca2 100755 --- a/tools/port/check-all +++ b/tools/port/check-all @@ -126,6 +126,14 @@ step menu-audio must-pass env OUT="$OUT/audio" tools/port/verify-menu-audi # stick fired once per jitter. step input-map must-pass tools/port/verify-input step input-control must-pass tools/port/verify-input --control +# 🔴 ADDED 2026-09-02 after a human found the splash frozen while THREE checks +# here were green. The frozen sweep proved a pose could be drawn, the settled +# comparison scored 0.01 % against the oracle (a frozen screen matches a settled +# reference perfectly -- that is what frozen means), and the fps counter counted +# frames drawn. All three measured throughput or a pose; none measured CHANGE. +# Same shape as InputEventAction bypassing the input map, two rows above. +step boot-motion must-pass tools/port/verify-motion +step motion-control must-pass tools/port/verify-motion --control # A stale index is worse than none: it answers "is this already decided?" with a # confident no. That is not hypothetical -- see the entry it was built after. step decisions-index must-pass tools/port/index-decisions --check diff --git a/tools/port/verify-motion b/tools/port/verify-motion new file mode 100755 index 00000000..0abdf900 --- /dev/null +++ b/tools/port/verify-motion @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# Does the boot ANIMATE, or does it draw the same picture very fast? +# +# tools/port/verify-motion # assert +# tools/port/verify-motion --control # can it fail? +# +# 🔴 WHY THIS EXISTS. A human on a 140 fps GPU: *"the port does no blur +# animation at all, the logos just switch."* Three checks this port already had +# were green at the time, and all three were blind the same way: +# +# frozen sweep (`--time=`) proves the renderer CAN draw pose N. It drives the +# clock by hand and never runs the animation. +# settled comparison scored 0.01 % against the oracle. A screen frozen +# 84 % of the time matches a settled reference +# PERFECTLY -- that is what frozen means. +# achieved-fps counter counts frames DRAWN. Drawing identical pixels 25 +# times a second scores exactly like animating. +# +# Every one measured throughput or a pose. **None measured CHANGE.** Same shape +# as `InputEventAction` bypassing the input map: the instrument sat below the +# thing that was broken, so the breakage could not appear in it. +# +# This films a REAL boot -- no `--time`, no pinning -- and hands it to +# `tools/motion-census`, which measures change and nothing else. +# +# ⚠️ WHAT IT CANNOT DO. It is the liveness half only. A wrong ramp that moves +# every frame passes here. Correctness stays with `verify-capture` against the +# oracle, and the two are complementary: one screen can pass either alone. +set -euo pipefail +cd "${PROJECT_DIR:-/work}" +export DISPLAY="${DISPLAY:-:97}" +OUT="${OUT:-${TMPDIR:-/tmp}/verify-motion}" +INTERVAL=0.05 + +# The publisher splash declares its whole build-in over t=0..45 -- 0.75 s at +# 60 units/s -- and then holds. So the FIRST second of the boot is where a +# frozen build-in shows up, and it is the only window this asserts on. +# +# The bar is 60 % of adjacent frame-pairs moving in that window, and BOTH SIDES +# WERE MEASURED rather than one measured and one assumed -- the one operator in +# `ScreenView.pose_at` was reverted, this check run against the defect, and the +# operator restored: +# +# broken (pose_at ASSIGNED the settle instant) 40 % -- and it FAILED +# fixed (clamps to it) 86 % -- and it passed +# +# 60 sits mid-gap: 20 points above the defect, 26 below the fix. That is why it +# is a floor and not a tuned threshold, and it is deliberately NOT set near the +# passing value -- a check that only passes at exactly today's number fails on +# the next legitimate change and teaches people to edit the bar. +# +# 🔴 THE FIRST VERSION CLAIMED "~40 POINTS OF CLEARANCE ON BOTH SIDES" AND HAD +# NOT MEASURED THE BROKEN CASE. With a 1.0 s window the real clearance was 5 +# points, because that window includes 0.25 s of legitimate hold and dilutes the +# signal. The window is now the DECLARED build-in -- publisher t=0..45, 0.75 s +# at 60 units/s -- so it asks about the interval the disc says is animating and +# nothing else. A bar justified by an unmeasured number is the same defect this +# whole check exists to catch, one level up. +WINDOW=0.75 +BAR=60 + +films() { # $1 = dir + rm -rf "$1"; mkdir -p "$1" + timeout 120 godot --path port -- --boot --skip-at=1 \ + --film="$1/f" --film-interval="$INTERVAL" >"$1/boot.log" 2>&1 || true +} + +moving_pct_in_window() { # $1 = dir -- % of adjacent pairs that MOVED, first $WINDOW seconds + python3 - "$1" "$INTERVAL" "$WINDOW" <<'PY' +import sys, glob, os, importlib.util, importlib.machinery +d, interval, window = sys.argv[1], float(sys.argv[2]), float(sys.argv[3]) +frames = sorted(glob.glob(os.path.join(d, "f_*.png"))) +n = int(window / interval) + 1 +frames = frames[:n] +if len(frames) < 3: + print("0"); raise SystemExit +# Reuse motion-census's own loader and floor rather than re-deriving them: a +# second implementation of "did it move" is a second thing to be wrong. +sys.path.insert(0, os.path.join(os.environ.get("PROJECT_DIR", "/work"), "tools")) +spec = importlib.util.spec_from_loader( + "mc", importlib.machinery.SourceFileLoader( + "mc", os.path.join(os.environ.get("PROJECT_DIR", "/work"), "tools", "motion-census"))) +mc = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mc) +from pathlib import Path +prev, moved, total = None, 0, 0 +for f in frames: + cur = mc.load(Path(f)) + if prev is not None: + delta = sum(abs(a - b) for a, b in zip(cur, prev)) / len(cur) + total += 1 + if delta > mc.MOVED: + moved += 1 + prev = cur +print("%d" % (100 * moved / total if total else 0)) +PY +} + +echo "boot liveness: films a real boot and measures CHANGE, not throughput" + +# 🔴 THE CONTROL RUNS FIRST AND IS NOT OPTIONAL. `motion-census --selftest` +# drives a synthetic fade, a switch and a frozen film through the same loader +# and the same floor this check uses. If it cannot separate those three, every +# number below is decoration. +if ! tools/motion-census --selftest >"$OUT.selftest.log" 2>&1; then + echo " 🔴 motion-census --selftest FAILED -- the detector cannot tell a fade" + echo " from a switch, so nothing it reports about the boot means anything." + sed 's/^/ /' "$OUT.selftest.log" + exit 2 +fi +echo " census selftest ok (fade / switch / frozen separated)" + +if [ "${1:-}" = "--control" ]; then + # A frozen film must FAIL this check. Built by repeating one real boot frame, + # so it has the port's own pixels and differs from a passing run in exactly + # one property: nothing changes. + films "$OUT/live" + ctl="$OUT/frozen"; rm -rf "$ctl"; mkdir -p "$ctl" + # NOT `ls | head`: under `set -o pipefail` head closes the pipe, ls takes + # SIGPIPE and the script exits 141 before it ever asserts anything. Cost one + # run to notice, and a check that dies before checking looks a lot like a + # check that passed. + local_frames=("$OUT"/live/f_*.png) + src="${local_frames[0]}" + for i in $(seq -w 0 24); do cp "$src" "$ctl/f_0$i.png"; done + pct=$(moving_pct_in_window "$ctl") + if [ "$pct" -lt "$BAR" ]; then + echo " frozen film is REJECTED ok ${pct}% moving, bar ${BAR}%" + echo + echo "the check fails on a film that does not move" + exit 0 + fi + echo " frozen film is REJECTED 🔴 FAILED ${pct}% moving -- it passed, so" + echo " this check cannot detect the defect it was written for." + exit 1 +fi + +films "$OUT/live" +grep -m1 -E "fps achieved" "$OUT/live/boot.log" | sed 's/^ */ /' || true +pct=$(moving_pct_in_window "$OUT/live") +printf ' %-25s %s %d%% of pairs moved in the first %.1fs, bar %d%%\n' \ + "build-in moves" "$([ "$pct" -ge "$BAR" ] && echo ok || echo '🔴 FAILED')" \ + "$pct" "$WINDOW" "$BAR" +[ "$pct" -ge "$BAR" ] || { + echo + echo "🔴 the boot draws its first second without changing. That is the" + echo " 2026-09-02 defect: poses not advancing while the clock does." + echo " Films are in $OUT/live -- run tools/motion-census on them." + exit 1 +} +echo +echo "the boot's build-in animates"