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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
27
tools/re-capture/present_rate_session.sh
Executable file
27
tools/re-capture/present_rate_session.sh
Executable file
@@ -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 >/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 </dev/null >/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" </dev/null >"$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}"
|
||||
111
tools/re-capture/present_rate_vs_load.py
Normal file
111
tools/re-capture/present_rate_vs_load.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user