#!/usr/bin/env bash # Headless Pitchwell smoke — no Godot GUI. Uses game.sim.match (20 Hz). set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" N_TICKS="${1:-40}" STUB="${ROOT}/assets/cinematics/first-contact/out/first-contact-stub.mp4" fail() { echo "SMOKE FAIL $*" >&2 exit 1 } if [[ -f "${STUB}" ]]; then if command -v ffprobe >/dev/null 2>&1; then wh="$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 "${STUB}" || true)" w="${wh%%,*}"; h="${wh##*,}" if [[ "${w}" != "1280" || "${h}" != "720" ]]; then fail "stub ${STUB} size ${w}x${h} want 1280x720" fi else echo "SMOKE WARN ffprobe missing; skip stub size check" >&2 fi fi OUT="$( N_TICKS="${N_TICKS}" python3 - <<'PY' import os, sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if "__file__" in dir() else Path.cwd() # when stdin script, cwd is ROOT from caller sys.path.insert(0, str(Path.cwd())) from game.sim.catalog import FACTION_STAFF, HZ, START_PITCH from game.sim.match import open_match n = int(os.environ["N_TICKS"]) m = open_match() staff = m.side(FACTION_STAFF) # claim well via finished font so seep applies (match open alone has neutral well) r = m.enqueue_building(FACTION_STAFF, "pitch_font", well_index=0) if r != "ok": print("enqueue font", r, file=sys.stderr) sys.exit(1) # finish 15s build m.tick_hz(int(15 * HZ) + 1) if not m.wells[0].claimed: print("well not claimed", file=sys.stderr) sys.exit(1) before = staff.pitch m.tick_hz(n) # Staff harvest 0.85 * 0.4/s expect_gain = n * (0.4 / HZ) * 0.85 got = staff.pitch - before if abs(got - expect_gain) > 1e-6: print(f"pitch gain {got} != expect {expect_gain}", file=sys.stderr) sys.exit(1) if not staff.hq_alive: print("HQ missing", file=sys.stderr) sys.exit(1) print(f"ticks={n} hz={HZ} pitch={staff.pitch:.4f} hq=1 gain={got:.4f}") PY )" || fail "sim" echo "SMOKE PASS headless ${OUT}" exit 0