port: a check that measures CHANGE, run against the defect it was written for
Closes the two items the play-test left after the fix landed. 1. tools/port/verify-motion, wired into check-all with its control. It films a REAL boot -- no --time, no pinning -- and hands it to tools/motion-census. The window is 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. 🔴 BOTH SIDES MEASURED, not one measured and one assumed. I reverted the single operator in pose_at, ran this check against the defect, and restored it: broken (ASSIGNS the settle instant) 40 % and it FAILED fixed (clamps to it) 86 % and it passed The bar of 60 sits mid-gap: 20 points above the defect, 26 below the fix. 🔴 AND MY FIRST VERSION OF THAT BAR WAS JUSTIFIED BY A NUMBER I HAD NOT MEASURED. It claimed "~40 points of clearance on both sides"; 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. A bar defended by an unmeasured number is this whole check's own defect, one level up. Corrected with the measurements and the sequence stated. The control runs FIRST and is not optional: motion-census --selftest drives a fade, a switch and a frozen film through the same loader and floor. If it cannot separate those three, every number the check reports is decoration. Then the --control mode builds a frozen film from the port's own pixels -- one real boot frame repeated -- and requires it to FAIL. It scores 0 %. One defect in the check itself, caught by running it: `ls | head` under `set -o pipefail` exits 141 before asserting anything, and a check that dies before checking looks a lot like a check that passed. 2. BLOCKED.md H2 corrected from ✅ to 🟡, against myself. The MECHANISM half stands -- no post-process pass, the blur is a baked companion texture. The BEHAVIOUR half was false and the row asserted it: I wrote "the port draws all seven quads -- verified by a frozen sweep". 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 and says nothing about the poses being drawn in sequence while running. All three of my instruments passed the defect -- frozen sweep, a 0.01 % settled comparison (a frozen screen matches a settled reference PERFECTLY), and an achieved-fps counter (identical pixels 25x/s score like animating). Every one measured throughput or a pose; none measured CHANGE. Same shape as InputEventAction bypassing the input map. Not settled: the ~1.0-1.2 menu residual; findings 3 and 4's remaining halves. Both were explicitly deferred by the human until this was fixed, and it now is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
152
tools/port/verify-motion
Executable file
152
tools/port/verify-motion
Executable file
@@ -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"
|
||||
Reference in New Issue
Block a user