port: delete exit_ramp_units, invert the format's own rule, and guard a scale-0 leaf

FOUR THINGS, and the first is what MISSION section 3 calls the measure of
progress.

DELETED `exit_ramp_units` AND `exit_ramp_seconds`. They were authored because the
disc had no time slot on a group's final keyframe, so the ramp into it was the
one unknown duration per screen. Under the corrected record layout that keyframe
does not exist -- a group is an 8-byte header then frames x {u32 time; 36-byte
pose} and every pose is timed. VERIFIED DEAD BEFORE DELETING: setting it to 9999
(166 s) moved the boot's transitions by 0.04 s, which is wall-clock jitter, and
both uses in ScreenView are gated on a condition that no longer fires on any of
the export's 866 keyframes.

INVERTED THE FORMAT'S OWN RULE. `check.rs` enforced "the final keyframe has no
`t`; the disc has no time slot there" and FORMAT.md stated it. Both are now
backwards, and the validator fired 150 times on a re-export. I had not run
`check` between pinning the tag and measuring against the oracle -- the pixel
harness was green while the format validator was failing on every screen with a
multi-keyframe group. A correctness harness does not replace a format one; they
fail at different layers.

GUARDED A SCALE-0 LEAF, which the Decoder hit in its own renderer: its leaf
branch marked the element drawn unconditionally while the blit returned early on
zero scale, so a scale-0 leaf suppressed its parent and blanked the element --
live on all four loading screens. This port did not have the bug only because
authored/rendering.json happens not to list pgloading_loop5. That is an accident
of a gate written for another reason, not a defence, so `_draw_leaf` now reports
whether it drew and `_draw` falls back to the parent.

ISOLATED THE PACING QUESTION rather than leaving it as a suspected regression.
Legacy association: publisher 4.70 agrees, developer 3.92 DIFFERS. Corrected:
publisher 4.26 DIFFERS, developer 3.62 agrees. Both misses are ~0.03 s outside a
composite bound. The association traded which screen is marginally out; it did
not regress the pacing.

Bumped the pin c -> d for the parser and audio changes. Its headline renderer
change does not reach this port: sylpheed-cli builds from the workspace crate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-29 19:19:46 +00:00
parent 16ab24480d
commit ace071dd7b
8 changed files with 189 additions and 102 deletions

View File

@@ -1,91 +1,105 @@
#!/usr/bin/env bash
# Measure the port's own VISIBLE SPANS and put them beside the oracle's dwells.
# Check the port's boot pacing against captures of the real game.
#
# tools/port/verify-dwell
#
# WHY THIS IS A TOOL AND NOT A ONE-OFF. Doing this by hand once already refuted a
# 🔴 I had filed myself. `docs/port/BLOCKED.md` said `rest.t` was the wrong
# settle landmark AND that "everything the sequencer paces off it is therefore
# late". The first half is true; the second was wrong, and I nearly went and
# "fixed" screens that already matched the game to 0.05 s.
# WHY THIS IS A TOOL AND NOT A ONE-OFF. Doing it by hand once already refuted a
# 🔴 I had filed myself: `docs/port/BLOCKED.md` said `rest.t` was the wrong settle
# landmark AND that "everything the sequencer paces off it is therefore late".
# The first half is true; the second was wrong, and I nearly re-paced screens
# that already matched the game to 0.05 s.
#
# ⚠️ THE TRAP THAT CAUSED IT, AND THE REASON THIS SCRIPT EXISTS AT ALL:
# a port's TRANSITION TIMESTAMPS and the oracle's VISIBLE SPANS are not the same
# quantity. They differ by the exit ramp plus the black hold -- about 0.6 s here,
# which was the entire discrepancy. This corpus has been bitten by the identical
# confusion before, on the plate delay: "timed from where build 4 stops
# animating, not from where it first appears -- measured the other way the two
# runs differ by 0.48 s against 6 ms." So this measures the port the way the
# oracle was measured: first ink to last ink.
# ⚠️ A PORT'S TRANSITION INTERVAL IS NOT THE ORACLE'S VISIBLE SPAN. They differ
# by the black hold between screens, and confusing the two cost this corpus 0.6 s
# once and 0.48 s on the plate delay. So the comparison here is explicit: the
# port's interval is checked against the oracle's span PLUS the measured hold.
#
# THE EXPECTED NUMBERS BELOW ARE THE ORACLE'S, NOT THE PORT'S. They are three
# cold boots from `docs/re/boot-order-and-splash-dwell.md`, first-visible to
# last-visible, and they are quoted here as a test fixture. Nothing in the port
# derives them and nothing may.
# 🔴 AND THE VERDICT DOES NOT COME FROM THE FILMSTRIP ANY MORE. It used to
# measure ink spans from `--film` frames. The boot's black hold is 0.17-0.23 s
# (HANDOFF Q7) -- shorter than the 0.25 s cadence meant to observe it -- so when
# the black frame fell between samples two screens merged into one span and this
# tool reported `developer logos` as 93 s against an oracle of 3.5 s. Filming at
# 0.1 s made it WORSE: 2.5x the screenshots slows the run enough that the capture
# catches up in bursts, and the publisher span came back as 7.80 s.
#
# The sequencer already knows exactly when it changed screens and prints it.
# Sampling a picture to rediscover a number the program can state is how this
# went wrong. The filmstrip is kept, and marked advisory.
#
# THE EXPECTED NUMBERS ARE THE ORACLE'S, NOT THE PORT'S: three cold boots from
# `docs/re/boot-order-and-splash-dwell.md`, quoted as a test fixture. Nothing in
# the port derives them and nothing may.
set -euo pipefail
cd "${PROJECT_DIR:-/work}"
export DISPLAY="${DISPLAY:-:97}"
OUT="${OUT:-$(mktemp -d)}"
mkdir -p "$OUT"
INTERVAL="${INTERVAL:-0.25}"
echo "running the boot (the intro is skipped -- the splashes are what this measures)"
timeout "${TIMEOUT:-200}" godot --path port --resolution 1280x720 -- \
--boot --skip-at=1 "--film=$OUT/f" >"$OUT/boot.log" 2>&1 || true
timeout "${TIMEOUT:-300}" godot --path port --resolution 1280x720 -- \
--boot --skip-at=1 "--film-interval=$INTERVAL" "--film=$OUT/f" \
>"$OUT/boot.log" 2>&1 || true
python3 - "$OUT" <<'PYEOF'
import glob, os, subprocess, sys
INTERVAL="$INTERVAL" python3 - "$OUT" <<'PYEOF'
import glob, os, re, subprocess, sys
out = sys.argv[1]
frames = sorted(glob.glob(os.path.join(out, "f_*.png")))
if not frames:
print("no frames -- is there a display? see", os.path.join(out, "boot.log")); raise SystemExit(2)
INTERVAL = 0.25 # boot.gd's --film cadence
means = []
for f in frames:
m = subprocess.run(["convert", f, "-colorspace", "Gray", "-format",
"%[fx:mean*255]", "info:"], capture_output=True, text=True)
means.append(float(m.stdout.strip() or 0))
INTERVAL = float(os.environ.get("INTERVAL", "0.25"))
HOLD_LO, HOLD_HI = 0.17, 0.23 # HANDOFF Q7, measured
# A frame is "ink" if anything at all is drawn. The splashes are dim -- surface
# means of 5/255 -- so the bar is ">0", not a percentage of full scale.
ink = [m > 0.0 for m in means]
spans, i = [], 0
while i < len(ink):
if ink[i]:
j = i
while j < len(ink) and ink[j]: j += 1
spans.append((i * INTERVAL, j * INTERVAL))
i = j
else:
i += 1
marks = []
for line in open(os.path.join(out, "boot.log"), errors="replace"):
m = re.match(r"\s+-> (\S+) at ([0-9.]+) s", line)
if m:
marks.append((m.group(1), float(m.group(2))))
if not marks:
print("no transitions in the boot log -- see", os.path.join(out, "boot.log"))
raise SystemExit(2)
# The oracle: three cold boots, first-visible to last-visible.
# docs/re/boot-order-and-splash-dwell.md. NOT the port's numbers.
ORACLE = [
("publisher wordmark", [4.297, 4.604, 4.370]),
("developer logos", [3.508, 3.503, 3.366]),
]
starts = [0.0] + [t for _, t in marks]
print()
print("%-20s %-12s %-26s %s" % ("screen", "port", "oracle (3 cold boots)", "verdict"))
print("%-20s %-14s %-26s %s" % ("screen", "port interval", "oracle span (3 boots)", "verdict"))
bad = 0
for k, (name, runs) in enumerate(ORACLE):
if k >= len(spans):
print("%-20s %-12s %-26s no such span in this run" % (name, "-", ""))
continue
a, b = spans[k]
d = b - a
lo, hi = min(runs), max(runs)
# Inside the oracle's own run-to-run spread, or within one film interval of
# it. The spread is the honest bar: three boots of the real game differ by
# 0.3 s, so agreeing more tightly than that would not mean anything.
ok = (lo - INTERVAL) <= d <= (hi + INTERVAL)
print("%-20s %-12s %-26s %s"
if k + 1 >= len(starts):
print("%-20s %-14s %s" % (name, "-", "no such transition this run")); continue
d = starts[k + 1] - starts[k]
lo, hi = min(runs) + HOLD_LO, max(runs) + HOLD_HI
ok = lo - 0.15 <= d <= hi + 0.15
bad += 0 if ok else 1
print("%-20s %-14s %-26s %s"
% (name, "%.2f s" % d, "%.3f / %.3f / %.3f" % tuple(runs),
"agrees" if ok else "DIFFERS"))
print()
print("port spans (first ink -> last ink), +/- %.2f s from the film cadence:" % INTERVAL)
for a, b in spans[:6]:
print(" %6.2f - %6.2f s (%.2f s)" % (a, b, b - a))
print()
print("⚠️ Read this beside the transition timestamps in the boot log, never")
print(" instead of them -- they differ by the exit ramp plus the black hold.")
print(" An interval is the oracle's SPAN plus the black hold (%.2f-%.2f s)," % (HOLD_LO, HOLD_HI))
print(" compared against span+hold with 0.15 s of slack for wall-clock jitter.")
print(" transitions:", ", ".join("%s@%.2f" % m for m in marks[:4]))
frames = sorted(glob.glob(os.path.join(out, "f_*.png")))[:120]
if frames:
means = [float(subprocess.run(["convert", f, "-colorspace", "Gray", "-format",
"%[fx:mean*255]", "info:"], capture_output=True, text=True).stdout or 0)
for f in frames]
ink = [m > 0.0 for m in means]
spans, i = [], 0
while i < len(ink):
if ink[i]:
j = i
while j < len(ink) and ink[j]: j += 1
spans.append((i * INTERVAL, j * INTERVAL)); i = j
else:
i += 1
print()
print(" advisory -- filmstrip ink spans at %.2f s, which CANNOT resolve a" % INTERVAL)
print(" %.2f-%.2f s hold and merges screens whenever it misses one:" % (HOLD_LO, HOLD_HI))
for a, b in spans[:4]:
print(" %6.2f - %6.2f s (%.2f s)" % (a, b, b - a))
raise SystemExit(1 if bad else 0)
PYEOF
rc=$?
echo "artifacts in $OUT"
exit $rc