#!/usr/bin/env bash
# terrain-bench.sh — DC-terrain generation benchmark harness.
#
# Turns a tee'd game log into objective LOD-generation numbers so "is it fixed?"
# becomes a number, not a screenshot. Parses the [MESHBENCH] (GPU per-stage
# timing + throughput) and [LODDIAG] (backlog/settle) lines emitted by the
# in-game diagnostics.
#
# Usage:
# # 1. run the game and capture a log (spawn, stand still ~20s, then move):
# hyper-tps 2>&1 | tee /tmp/tbench.log
# # 2. report:
# bash scripts/terrain-bench.sh /tmp/tbench.log
#
# Metrics reported:
# - throughput: GPU chunks meshed/sec (min / mean / max across intervals)
# - per-chunk time split: submit vs sync (the CPU<->GPU stall) vs readback
# - sync fraction: how much of per-chunk time is the serial sync stall (the §E target)
# - backlog: peak premesh PENDING (queued-but-unmeshed chunks)
# - convergence: did premesh PENDING ever return to ~0 (terrain settled)?
set -euo pipefail
LOG="${1:-}"
[ -z "$LOG" ] && { echo "usage: $0 <game-log>"; exit 1; }
[ -f "$LOG" ] || { echo "no such log: $LOG"; exit 1; }
echo "=== DC-terrain generation benchmark: $LOG ==="
VER=$(grep -oE "v6[0-9]+-[0-9-]+-[a-z0-9]+" "$LOG" | head -1 || true)
echo "build: ${VER:-unknown}"
echo ""
# --- MESHBENCH: throughput + per-stage timing ---
awk '
/\[MESHBENCH\] meshed=/ {
# meshed=+N in Tms => R chunks/s | per-chunk avg: submit=A sync=B readback=C total=D ms | slowest chunk=M ms
for (i = 1; i <= NF; i++) {
if ($i ~ /^=>/) { rate = $(i+1); n++; sum_rate += rate; if (rate>max_rate||n==1) max_rate=rate; if (rate<min_rate||n==1) min_rate=rate }
if ($i ~ /^submit=/) { s=$i; sub(/submit=/,"",s); sum_sub += s; ns++ }
if ($i ~ /^sync=/) { s=$i; sub(/sync=/,"",s); sum_syn += s }
if ($i ~ /^readback=/) { s=$i; sub(/readback=/,"",s); sum_rb += s }
}
}
END {
if (n == 0) { print " (no [MESHBENCH] samples — old build or CPU meshing)"; exit }
printf " throughput : %.0f chunks/s mean (min %.0f / max %.0f) over %d intervals\n", sum_rate/n, min_rate, max_rate, n
if (ns > 0) {
as=sum_sub/ns; ay=sum_syn/ns; ab=sum_rb/ns; at=as+ay+ab
printf " per-chunk : submit %.2fms | SYNC %.2fms | readback %.2fms | total %.2fms\n", as, ay, ab, at
if (at > 0) printf " sync frac : %.0f%% of per-chunk time is the serial CPU<->GPU sync stall (<-- the §E target)\n", 100*ay/at
}
}
' "$LOG"
echo ""
# --- CHURN: wasted re-meshing (the real deal-breaker) ---
awk '
/\[CHURN\] discarded=/ {
for (i = 1; i <= NF; i++) {
if ($i ~ /^discarded=\+/) { s=$i; sub(/discarded=\+/,"",s); d_sum+=s; n++ }
if ($i ~ /^recenters=\+/) { s=$i; sub(/recenters=\+/,"",s); r_sum+=s }
}
# "(P% of meshed wasted)" — the P is field after discarded=+N
for (i = 1; i <= NF; i++) if ($i ~ /^\(/ ) { p=$i; gsub(/[(%]/,"",p); if (p+0>peak_pct) peak_pct=p+0; pct_sum+=p+0 }
}
END {
if (n==0) { print " (no [CHURN] samples — old build)"; exit }
printf " wasted work : %d meshes discarded over run, %d recenters (avg %.0f%% of meshing wasted, peak %.0f%%)\n", d_sum, r_sum, pct_sum/n, peak_pct
if (pct_sum/n > 25) print " >>> CHURN IS THE BOTTLENECK: most meshing is thrown away and redone (fix churn before throughput)"
}
' "$LOG"
echo ""
# --- EDIT: CSG edit-to-visible latency (demo-critical) ---
awk '
/\[EDIT\] chunks_remeshed=/ {
for (i = 1; i <= NF; i++) if ($i ~ /^latency=/) { s=$i; sub(/latency=/,"",s); lat_sum+=s; n++; if (s+0>mx) mx=s+0 }
}
END {
if (n==0) { print " csg edits : (no edits during run — do a few CSG edits to benchmark responsiveness)"; exit }
printf " csg edits : %d edit-remesh intervals, avg %.0f frames (~%.0fms) edit->visible, worst %.0f frames (~%.0fms)\n", n, lat_sum/n, (lat_sum/n)*16.6, mx, mx*16.6
if ((lat_sum/n)*16.6 > 100) print " >>> EDITS ARE SLOW: edit-to-visible latency is high (likely starved by streaming backlog)"
}
' "$LOG"
echo ""
# --- LODDIAG: backlog + convergence ---
awk '
/\[LODDIAG\] leaves=/ {
pend=0; gen=0
for (i = 1; i <= NF; i++) {
if ($i ~ /^PENDING=/) { s=$i; sub(/PENDING=/,"",s); pend=s }
if ($i ~ /^GEN=/) { s=$i; sub(/GEN=/,"",s); gen=s }
}
total=pend+gen
n++
if (total > peak) peak=total
if (total <= 2) settled++
}
END {
if (n == 0) { print " (no [LODDIAG] samples)"; exit }
printf " peak backlog: %d premesh chunks queued+in-flight\n", peak
printf " convergence : %d/%d intervals at ~0 backlog (%s)\n", settled, n, (settled>0 ? "terrain DID settle at least once" : "NEVER settled — refinement never caught up")
}
' "$LOG"
echo ""
echo "Interpretation: a high sync fraction + high peak backlog = the single-thread"
echo "serial submit+sync bottleneck (§E). Deferred-readback ring should drop the sync"
echo "fraction and raise throughput; re-run this to confirm the number moved."