diff --git a/tools/autoplay-batch.sh b/tools/autoplay-batch.sh new file mode 100755 index 00000000..c63d2ab1 --- /dev/null +++ b/tools/autoplay-batch.sh @@ -0,0 +1,174 @@ +#!/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 + + result_file="$seed_dir/result_${seed}.json" + if [ -f "$result_file" ]; then + echo "[seed $seed] OK — result written to $result_file" + else + echo "[seed $seed] MISSING result_${seed}.json (Task 1 may not be complete, or game crashed)" >&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