magicciv/tools/autoplay-batch.sh
Natalie 979cd0ca26 feat(@projects/@magic-civilization): add combat and city tracking stats
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-16 17:51:23 -07:00

178 lines
6.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# autoplay-batch.sh — Run auto_play N times with different seeds and collect result JSON files.
#
# Usage: tools/autoplay-batch.sh [count=3] [turn_limit=500] [results_dir=/tmp/autoplay_batch]
#
# Environment:
# AUTOPLAY_HOST — If set (e.g. "lilith@apricot.local"), run each game via SSH using
# /tmp/run_ap3.sh on the remote host and scp results back.
# If unset, run locally via flatpak (Linux only).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
GAME_DIR="$PROJECT_DIR/src/game"
COUNT="${1:-3}"
TURN_LIMIT="${2:-500}"
RESULTS_DIR="${3:-/tmp/autoplay_batch}"
if ! [[ "$COUNT" =~ ^[0-9]+$ ]] || [ "$COUNT" -lt 1 ]; then
echo "ERROR: count must be a positive integer (got '$COUNT')" >&2
exit 2
fi
if ! [[ "$TURN_LIMIT" =~ ^[0-9]+$ ]] || [ "$TURN_LIMIT" -lt 1 ]; then
echo "ERROR: turn_limit must be a positive integer (got '$TURN_LIMIT')" >&2
exit 2
fi
AUTOPLAY_HOST="${AUTOPLAY_HOST:-}"
SAFETY_TIMEOUT=$(( TURN_LIMIT * 2 + 300 ))
# IMPORTANT: Flatpak sandboxes /tmp — result files written from inside the
# sandbox to /tmp silently disappear. Use $HOME/tmp for local flatpak runs.
if [ -z "$AUTOPLAY_HOST" ] && [[ "$RESULTS_DIR" == /tmp/* ]]; then
RESULTS_DIR="$HOME/tmp/autoplay_batch"
echo "INFO: Local flatpak run — redirecting results dir to $RESULTS_DIR (flatpak cannot write to /tmp)" >&2
fi
mkdir -p "$RESULTS_DIR"
echo "============================================================"
echo "Autoplay Batch: $COUNT games, turn_limit=$TURN_LIMIT"
echo "Results: $RESULTS_DIR"
if [ -n "$AUTOPLAY_HOST" ]; then
echo "Mode: remote SSH ($AUTOPLAY_HOST)"
else
echo "Mode: local flatpak"
fi
echo "Safety timeout: ${SAFETY_TIMEOUT}s per game"
echo "============================================================"
_kill_stale_procs() {
# Kill stale weston/godot from previous runs (local only)
pkill -f "weston.*godot-headless" 2>/dev/null || true
pkill -f "org.godotengine.Godot" 2>/dev/null || true
sleep 0.5
}
_run_local() {
local seed="$1"
local seed_dir="$2"
if ! command -v flatpak >/dev/null 2>&1; then
echo "ERROR: flatpak not installed. Set AUTOPLAY_HOST to run on a remote Linux host." >&2
exit 1
fi
_kill_stale_procs
echo "[seed $seed] Starting weston (headless)..."
WESTON_SOCKET="godot-headless-$$"
weston --backend=headless --socket="$WESTON_SOCKET" --width=1920 --height=1080 \
>"$seed_dir/weston.log" 2>&1 &
WESTON_PID=$!
sleep 1
echo "[seed $seed] Launching Godot (timeout ${SAFETY_TIMEOUT}s)..."
WAYLAND_DISPLAY="$WESTON_SOCKET" \
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
timeout "$SAFETY_TIMEOUT" flatpak run --user \
--socket=wayland \
--env=WAYLAND_DISPLAY="$WESTON_SOCKET" \
--env=AUTO_PLAY=true \
--env=AUTO_PLAY_SEED="$seed" \
--env=AUTO_PLAY_TURN_LIMIT="$TURN_LIMIT" \
--env=AUTO_PLAY_DIR="$seed_dir" \
org.godotengine.Godot \
--path "$GAME_DIR" \
--rendering-method gl_compatibility \
--headless \
>"$seed_dir/game.log" 2>&1 || {
local exit_code=$?
echo "[seed $seed] Godot exited with code $exit_code" >&2
}
kill "$WESTON_PID" 2>/dev/null || true
wait "$WESTON_PID" 2>/dev/null || true
}
_run_remote() {
local seed="$1"
local seed_dir="$2"
echo "[seed $seed] Running via SSH on $AUTOPLAY_HOST..."
# Build a remote results dir that run_ap3.sh can write to (not /tmp — Flatpak sandbox)
local remote_seed_dir
remote_seed_dir="\$HOME/tmp/autoplay_batch/seed_${seed}"
ssh "$AUTOPLAY_HOST" "
set -euo pipefail
mkdir -p '$remote_seed_dir'
if [ ! -f /tmp/run_ap3.sh ]; then
echo 'ERROR: /tmp/run_ap3.sh not found on $AUTOPLAY_HOST' >&2
exit 1
fi
AUTO_PLAY=true \
AUTO_PLAY_SEED='$seed' \
AUTO_PLAY_TURN_LIMIT='$TURN_LIMIT' \
AUTO_PLAY_DIR='$remote_seed_dir' \
bash /tmp/run_ap3.sh >'$remote_seed_dir/game.log' 2>&1
" || {
echo "[seed $seed] SSH run exited with error — see $seed_dir/game.log after scp" >&2
}
echo "[seed $seed] Fetching results from $AUTOPLAY_HOST..."
scp -r "$AUTOPLAY_HOST:\$HOME/tmp/autoplay_batch/seed_${seed}/." "$seed_dir/" \
>/dev/null 2>&1 || {
echo "WARNING: scp failed for seed $seed — result may be missing" >&2
}
}
# ── Main loop ────────────────────────────────────────────────────────────────
FAILED_SEEDS=()
for seed in $(seq 1 "$COUNT"); do
seed_dir="$RESULTS_DIR/seed_${seed}"
mkdir -p "$seed_dir"
echo ""
echo "[$(date +%H:%M:%S)] === Game $seed/$COUNT (seed=$seed) ==="
if [ -n "$AUTOPLAY_HOST" ]; then
_run_remote "$seed" "$seed_dir"
else
_run_local "$seed" "$seed_dir"
fi
# Look for timestamped result file (result_<stamp>_seed<N>.json) or legacy (result_<N>.json)
result_file="$(ls -1 "$seed_dir"/result_*_seed${seed}.json 2>/dev/null | tail -n1)"
if [ -z "$result_file" ] && [ -f "$seed_dir/result_${seed}.json" ]; then
result_file="$seed_dir/result_${seed}.json"
fi
if [ -n "$result_file" ] && [ -f "$result_file" ]; then
echo "[seed $seed] OK — result written to $result_file"
else
echo "[seed $seed] MISSING result file (no result_*_seed${seed}.json found)" >&2
FAILED_SEEDS+=("$seed")
fi
done
# ── Summary ──────────────────────────────────────────────────────────────────
echo ""
echo "============================================================"
PRODUCED=$(( COUNT - ${#FAILED_SEEDS[@]} ))
echo "Batch complete: $PRODUCED/$COUNT games produced result.json"
echo "Results: $RESULTS_DIR"
echo "============================================================"
if [ ${#FAILED_SEEDS[@]} -gt 0 ]; then
echo "ERROR: Missing result.json for seeds: ${FAILED_SEEDS[*]}" >&2
echo " Check game.log in each seed dir for details." >&2
exit 1
fi
exit 0