From 346e5574b4e1f906f7b2a34a37ae8f2d957f2931 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sun, 30 Aug 2026 23:28:02 +0000 Subject: [PATCH] re: strike the resolution contrast and the skipping claim -- both refuted by the port's own probe Their frame counter counts engine frames, an upper bound rather than a count: quiet, ADV drew 6480 engine frames across a 4123-frame video, so above that crossover it constrains nothing. The 28 %/47 % came from a contended run, so 'the player skips heavily' is unsupported. The 720p-vs-432p contrast is refuted and it is the version that reached this corpus twice. Quiet, both videos run +6.7 %..+6.9 %, 5 runs, resolution-independent. The -0.5 % was contention, not resolution. My own error in the thread is kept rather than superseded: I corrected a correct entry on an argument. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- docs/re/METHOD.md | 55 +++++------ docs/re/ui-keyframe-time-unit.md | 10 +- tools/re-capture/present_rate_session.sh | 27 ++++++ tools/re-capture/present_rate_vs_load.py | 111 +++++++++++++++++++++++ 4 files changed, 167 insertions(+), 36 deletions(-) create mode 100755 tools/re-capture/present_rate_session.sh create mode 100644 tools/re-capture/present_rate_vs_load.py diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index c753448b..89dda401 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -2469,37 +2469,38 @@ elapsed clock time stays on schedule by doing less work: a uniformly starved clo presents fewer frames per real second and still finishes in exactly the media's duration. **The failure produces the appearance of success.** -🔴 **CORRECTED TWICE, and the second correction restores the first reading.** -They argued the overrun was itself proof nothing was skipped — a player dropping -frames finishes early or on time, and theirs ran **146.6 s for 137.44 s of media**. -I accepted that and rewrote this entry. **Then they measured it**, counting the -frames the engine actually drew: +🔴 **CORRECTED THREE TIMES. The settled position is that NEITHER of us knows +whether their player skips, and the contrast that started this is refuted.** -| | engine frames | span | frames in media | presented | -|---|---|---|---|---| -| `S00A` 768×432 | 775 | 93.33 s | 2 813 | **28 %** | -| `ADV` 1280×720 | 1 941 | 140.77 s | 4 123 | **47 %** | +The sequence, kept because the shapes differ: they argued the overrun proved +nothing was skipped; I accepted the argument and rewrote a correct entry; they +measured engine frames and reported the player presenting 28 % / 47 %; then they +made the probe permanent and it corrected them twice more. -**The player skips, heavily.** `S00A` held real time *because* it dropped three -frames in four — the mechanism the argument said was absent. So the reading above -was right, their argument was wrong, and **my correction of it was wrong**. +* ❌ **"the player skips, heavily" is not supported.** The frame counter counts + *engine* frames, which is an **upper bound**, not a count — quiet, `ADV` drew + **6 480 engine frames across a 4 123-frame video**, 44 fps against the media's + 30, and above that crossover it constrains nothing. The 28 % came from a + **contended** run. +* ❌ **The 720p-versus-432p contrast is refuted, and it is the finding that + reached this corpus twice.** Quiet, **both videos run +6.7 %…+6.9 %**. The + −0.5 % that made the small clip look like it "kept real time" was a contended + run. Nothing about resolution survives; struck here and in + [`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md). +* ✅ **What survives is sturdier than either claim**: playback runs + **+6.7 %…+6.9 % long, 5 runs, both videos, quiet** — a real deficit in that + container, resolution-independent. -⚠️ **I corrected a correct entry on an argument, having just written on this page -that a consumption counter is what makes an audit.** Their inference sounded right -and I did not ask what would measure it; the measurement was four lines. **An -argument from an absence — "no overrun would be visible if frames were dropped" — -is not evidence, and accepting one is the same error as making one.** +⚠️ **My own error in this thread stands and is not superseded**: I corrected a +correct entry on an *argument*, on a page where I had just written that a +consumption counter is what makes an audit. Accepting an argument from an absence +is the same error as making one. -📌 Their own limit, which they stated and I would not have caught: the probe counts -**presented** frames, not **decoded** ones. Theora is inter-frame predicted, so a -decoder must decode frames it never shows. This refutes *"presented every frame"* -and leaves *"decoded every frame"* unmeasured. `read_offset` is a consumption -counter precisely because it cannot have that gap. - -🔴 **And their figure is a spread, not a value**: three `ADV` runs gave **146.42, -146.60, 140.77 s** → **+2.4 %…+6.7 %, n=3, load-dependent**. It reached this corpus -twice as a flat "+6.7 %". The qualitative finding — heavy decode falls behind, -light does not — survives as a **lower bound measured under skipping**. +📌 **And the companion rule, which is theirs**: their third correction came from a +confound they created themselves — a test suite running alongside the run being +timed, worth a **7-percentage-point swing**, larger than most effects either of us +reports. So: ask what the quantity can be **skipped** by, and ask **what else was +running**. ⚠️ My audio measurement survives that objection, and it is worth being precise about *why*, because the two look alike: `input_buffer_read_offset` is a diff --git a/docs/re/ui-keyframe-time-unit.md b/docs/re/ui-keyframe-time-unit.md index ec47c536..4612e00f 100644 --- a/docs/re/ui-keyframe-time-unit.md +++ b/docs/re/ui-keyframe-time-unit.md @@ -430,15 +430,7 @@ there is no way to select the old reading today, and nothing to set.** ## ⚠️ 2026-08-30 — "the game presents at 27.6 fps" is not separated from "my container runs the guest slow" -`sylpheed-port` warned that their boot timings carry a host deficit — three `ADV` -runs at 146.42 / 146.60 / 140.77 s against 137.44 s of media, i.e. -**+2.4 %…+6.7 %, n=3, load-dependent** (it reached this page first as a flat -"+6.7 %"; they corrected it to a spread). ⚠️ And it is a **lower bound measured -under skipping**: their player presents only **28–47 %** of the media's frames, so -the deficit is what remains *after* dropping three frames in four on the light -clip — and that any comparison between their seconds and -mine must go through **media length, not wall clock**. Checking whether that -reaches this page: **it does, and to the page's own headline.** +`sylpheed-port` warned that their boot timings carry a host deficit — **+6.7 %…+6.9 % long, 5 runs, both videos, quiet, resolution-independent**. ⚠️ This reached this page twice in superseded forms: first as a flat **+6.7 %**, then as a spread **+2.4 %…+6.7 %** with a 720p-versus-432p contrast. **Both are struck** — the low figure came from a *contended* run, and nothing about resolution survives. The deficit is real; the mechanism they first described is not Every rate here is a frame count over a wall-clock window on one container: diff --git a/tools/re-capture/present_rate_session.sh b/tools/re-capture/present_rate_session.sh new file mode 100755 index 00000000..6faa628b --- /dev/null +++ b/tools/re-capture/present_rate_session.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Boot, wait for the SETTLED title with the corpus's own gate, then measure the +# presented-frame rate idle and under load. +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +SD="$(cd "$(dirname "$0")" && pwd)" +OUT="${OUT:-/sylph-home/re/presentrate}"; mkdir -p "$OUT" +LOG="$OUT/canary.stdout" +bash "$SD/ensure_single_emulator.sh" +if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then + rm -f "/tmp/.X${DISPLAY#:}-lock" 2>/dev/null || true + nohup bash -c 'Xvfb "$0" -screen 0 1280x720x24 -ac -nolisten tcp \ + +extension GLX +extension RANDR >/tmp/xvfb98.log 2>&1' "$DISPLAY" /dev/null 2>&1 & + for _ in $(seq 1 50); do xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break; done + nohup env DISPLAY="$DISPLAY" HOME=/sylph-home openbox /tmp/openbox98.log 2>&1 & +fi +XUID="${SYLPH_XUID:-$(ls "${XENIA_CONTENT:-$HOME/.local/share/Xenia/content}" 2>/dev/null | head -1)}" +[ -n "$XUID" ] || { echo "NO PROFILE"; exit 2; } +echo "── EFFECTIVE CONFIG ──" +echo " out=$OUT profile=$XUID" +echo " gate = wait_plate_pulse.py, CALLED not reimplemented" +cd /sylph-home/re +nohup run-canary --apu=sdl --log_mask=13 --log_level=2 \ + --logged_profile_slot_0_xuid="$XUID" "$LOG" 2>&1 & +python3 "$SD/wait_plate_pulse.py" 900 || { echo "NEVER REACHED THE SETTLED TITLE"; exit 1; } +echo "settled title reached" +python3 "$SD/present_rate_vs_load.py" "$OUT" "${SECS:-30}" diff --git a/tools/re-capture/present_rate_vs_load.py b/tools/re-capture/present_rate_vs_load.py new file mode 100644 index 00000000..19bb6156 --- /dev/null +++ b/tools/re-capture/present_rate_vs_load.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Is "the game presents at 27.6 fps" a property of the GAME or of this container? + +ui-keyframe-time-unit.md reads 27.6-28.8 fps off frame counts over wall-clock +windows here, and the 8.5 % splash-dwell excess is the same number from the other +side. The audio clock is bounded at 0.985 +- 0.015 of real time +(container-audio-clock.txt), so a uniform slowdown is refuted -- but that bounds +the AUDIO path, and these are FRAME numbers. + +🔴 sylpheed-port's proposed instrument -- frames presented per audio sample +consumed, against a quartz reference -- IS NOT AVAILABLE HERE. This container has +no /dev/snd, no ALSA and no PulseAudio, so SDL's only backend is `dummy` and every +clock in reach is a software clock. There is no hardware rate to measure against. + +So use a different axis. A rate set by the GAME does not move with host load; a +rate set by STARVATION does. sylpheed-port demonstrated exactly this mechanism on +their box (720p +6.7 % vs 432p -0.5 %); this asks whether it operates on mine. + +MEASUREMENT: presented frames = frames that DIFFER from their predecessor in an +x11grab capture. The settled title free-runs two sweep leaves and a pulsing plate, +so every presented frame differs -- which is what makes counting them reliable +here and would not hold on a still screen. + +🔴 CONTROLS, both required: + * capture at 60 AND at 30 fps. If both are above the guest's rate they must + agree; if the 60 Hz figure is higher, the 30 Hz capture was undersampling and + neither number is a guest rate. + * the settled-title gate is CALLED (wait_plate_pulse.py), not reimplemented -- + a previous run sampled across the build-in because I rebuilt the gate from + memory and dropped its twelve-sample hold. + + present_rate_vs_load.py OUTDIR [seconds] +""" +import os, subprocess, sys, time +import numpy as np + +OUT = sys.argv[1] +SECS = float(sys.argv[2]) if len(sys.argv) > 2 else 30.0 +W, H = 1280, 720 +os.makedirs(OUT, exist_ok=True) + + +def count_distinct(rate, seconds, thresh=24): + """Frames differing from their predecessor, captured at `rate` fps.""" + p = subprocess.Popen( + ["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0", + "-video_size", f"{W}x{H}", "-i", ":98", "-r", str(rate), + "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], + stdout=subprocess.PIPE, bufsize=W * H * 3 * 4) + n = W * H * 3 + prev = None + got = distinct = 0 + t0 = time.time() + while time.time() - t0 < seconds: + b = p.stdout.read(n) + if len(b) < n: + break + a = np.frombuffer(b, np.uint8).reshape(H, W, 3) + got += 1 + if prev is not None and int((np.abs(a.astype(np.int16) - prev).max(axis=2) + > thresh).sum()) > 200: + distinct += 1 + prev = a.astype(np.int16) + el = time.time() - t0 + p.kill() + return got, distinct, el + + +def load_on(workers=4): + return [subprocess.Popen([sys.executable, "-c", + "\nwhile True: pass\n"]) for _ in range(workers)] + + +print("── measuring the presented-frame rate on the settled title ──", flush=True) +rows = [] +for rate in (60, 30): + got, dis, el = count_distinct(rate, SECS) + fps = dis / el + rows.append((f"capture {rate} fps, idle", got, dis, el, fps)) + print(f" capture {rate:2d} fps: {got} grabbed, {dis} distinct in {el:.1f}s " + f"-> {fps:.2f} presented fps", flush=True) + +print("── now under artificial CPU load ──", flush=True) +procs = load_on(4) +time.sleep(3) +try: + got, dis, el = count_distinct(60, SECS) + fps = dis / el + rows.append(("capture 60 fps, +4 busy cores", got, dis, el, fps)) + print(f" capture 60 fps + load: {got} grabbed, {dis} distinct in {el:.1f}s " + f"-> {fps:.2f} presented fps", flush=True) +finally: + for q in procs: + q.kill() + +with open(f"{OUT}/present-rate.tsv", "w") as f: + f.write("# condition\tgrabbed\tdistinct\tseconds\tpresented_fps\n") + for r in rows: + f.write(f"{r[0]}\t{r[1]}\t{r[2]}\t{r[3]:.2f}\t{r[4]:.3f}\n") + +print("\n================ RESULT ================") +for r in rows: + print(f" {r[0]:30} {r[4]:6.2f} fps") +if len(rows) == 3: + a, b, c = rows[0][4], rows[1][4], rows[2][4] + print(f"\n CONTROL 60 vs 30 Hz capture: {a:.2f} vs {b:.2f} " + f"({'agree' if abs(a-b) < 0.15*max(a,b) else 'DISAGREE — 30 Hz undersampled'})") + print(f" idle {a:.2f} -> loaded {c:.2f} = {100*(c-a)/a:+.1f} %") + print(" a rate set by the GAME does not move with host load;" + " a rate set by STARVATION does") +print("PRESENT RATE RUN DONE", flush=True)