#!/usr/bin/env bash
# frametime-load-test — does background tile generation cost frames?
#
# THE ONLY HARD CONSTRAINT on growing the world across peers is that generation
# must not affect gameplay. Everything else — throughput, peer counts, hours to
# bake — is secondary and has been argued about at length without this number.
#
# HOW IT WORKS. One run, three phases, so the comparison is within a single
# session rather than across runs:
#
# 1. BASELINE game idle, no load
# 2. LOADED N CPU tile generations in flight at `nice` level
# 3. RECOVERY load stopped — proves frame time returns, i.e. what we saw
# was the load and not drift or thermal ramp
#
# WHY IN ONE RUN. The GPU throttles 2100 -> 1410 MHz at 86 C and the same config
# has timed 8.07 to 24.35 s across runs. Comparing two separate launches would
# measure the card's mood. Only interleaved phases survive that, and RECOVERY is
# what catches it if they do not.
#
# THE LOAD IS THE REAL GENERATOR, not a synthetic spinner: tiles fetched from
# the CPU inference service, which is what would actually run in the background.
# A busy-loop would exercise the scheduler but not the memory bandwidth and
# cache pressure that are the likely mechanism.
#
# scripts/frametime-load-test.sh [--phase-seconds 60] [--jobs 2] [--nice 19]
#
# Then play normally — walk, fight, look around. An idle camera measures an idle
# renderer and answers nothing.
set -uo pipefail
PHASE=60
JOBS=2
NICE=19
URL="http://127.0.0.1:8830"
OUT="/tmp/frametime-$(date +%H%M%S).csv"
while [ $# -gt 0 ]; do
case "$1" in
--phase-seconds) PHASE="$2"; shift 2 ;;
--jobs) JOBS="$2"; shift 2 ;;
--nice) NICE="$2"; shift 2 ;;
--url) URL="$2"; shift 2 ;;
--out) OUT="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
if ! curl -s -m 3 -o /dev/null "$URL/coarse?i1=0&j1=0&i2=2&j2=2"; then
echo "FATAL: no CPU inference service at $URL" >&2
echo " start one: cd ../terrain-diffusion && .venv/bin/python -m \\" >&2
echo " terrain_diffusion.inference.api --device cpu --no-compile \\" >&2
echo " --seed 1234 --port 8830" >&2
exit 1
fi
echo "frametime-load-test"
echo " phases ${PHASE}s each: baseline / loaded / recovery"
echo " load ${JOBS} tile generations, nice ${NICE}, from ${URL}"
echo " frame log ${OUT}"
echo
echo "Launch the game in ANOTHER terminal with:"
echo
echo " HYPER_TPS_FRAMELOG=${OUT} ./scripts/dev-run.sh"
echo
echo "Then PLAY — walk, fight, look around. An idle camera measures an idle"
echo "renderer and answers nothing."
echo
read -r -p "Press enter once the map has loaded and you are moving... " _
mark () { echo "# phase $1 $(date +%s%3N)" >> "$OUT.phases"; echo " -> $1"; }
: > "$OUT.phases"
mark baseline
sleep "$PHASE"
mark loaded
PIDS=()
for i in $(seq 1 "$JOBS"); do
(
tx=$((1000 + i * 37))
while :; do
nice -n "$NICE" curl -s -m 600 -o /dev/null \
"$URL/terrain?i1=$((tx * 256))&j1=0&i2=$(((tx + 1) * 256))&j2=256&scale=1"
tx=$((tx + 1))
done
) &
PIDS+=($!)
done
sleep "$PHASE"
for p in "${PIDS[@]}"; do kill "$p" 2>/dev/null; done
# the curl children outlive their subshell; reap by process group
pkill -P $$ curl 2>/dev/null
mark recovery
sleep "$PHASE"
echo
echo "done. Stop the game, then:"
echo " python3 scripts/frametime-report.py $OUT"