#!/usr/bin/env bash # run_melee_strong_field.sh — drive the STRONG-FIELD melee A/B # (see common_libs/tests/measure_melee_strong_field.nim). # # Builds ONE frozen ModularBot (from `git archive HEAD`), then runs one process # per arm in parallel; each process owns its own Tank Royale server on an # ephemeral port. Output: //run.json (+ .bot.stdout.log). # # common_libs/tests/run_melee_strong_field.sh [outdir] # # Env: # MELEE_RUNS (default 12) # MELEE_ROUNDS (default 5) # MELEE_ARMS (default "pattern bb_ret bb_decay") # MELEE_FIELD (default "/tmp/tr_bots/Diamond,/tmp/tr_bots/Dookious,/tmp/tr_bots/GresSuffurd") # WAIT_ARENA minutes to wait for foreign battles (default 45) # # ARENA SERIALIZATION: refuses to start (or waits) while another job's battles # are running; never pkills a foreign process. Kills only its own process groups # on EXIT/INT/TERM. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "$HERE/../.." && pwd)" OUTDIR="${1:-/tmp/melee_strong_field}" RUNS="${MELEE_RUNS:-12}" ROUNDS="${MELEE_ROUNDS:-5}" ARMS_STR="${MELEE_ARMS:-pattern bb_ret bb_decay}" WAIT_ARENA="${WAIT_ARENA:-45}" export MELEE_FIELD="${MELEE_FIELD:-/tmp/tr_bots/Diamond,/tmp/tr_bots/Dookious,/tmp/tr_bots/GresSuffurd}" BIN="$HERE/measure_melee_strong_field" [[ -x "$BIN" ]] || { echo "ERROR: build the runner first: nim c --nimcache:/tmp/nc_j124 --path:common_libs $HERE/measure_melee_strong_field.nim" >&2; exit 1; } # ── arena serialization (bracketed patterns: pgrep must not match itself) ──── FOREIGN='[r]un_bridge_battle.sh|[r]obocode_shim.TrBattleCapture|[r]obocode_shim.LegacyBotBridge|[M]odularBot_bin|[m]easure_melee' foreign() { pgrep -af "$FOREIGN" 2>/dev/null | grep -v "$OUTDIR" || true; } if [[ -n "$(foreign)" ]]; then if (( WAIT_ARENA <= 0 )); then echo "[driver] ERROR: arena busy with another job's battles:" >&2 foreign | sed 's/^/ /' >&2 exit 3 fi echo "[driver] arena busy — waiting up to ${WAIT_ARENA} min:" foreign | sed 's/^/ /' deadline=$(( $(date +%s) + WAIT_ARENA * 60 )) while :; do sleep 60 if [[ -z "$(foreign)" ]]; then echo "[driver] arena free."; break; fi if (( $(date +%s) >= deadline )); then echo "[driver] ERROR: arena still busy after ${WAIT_ARENA} min; NOT starting." >&2 exit 3 fi echo "[driver] still busy at $(date +%H:%M:%S)" done fi mkdir -p "$OUTDIR" echo "[driver] building frozen ModularBot…" "$BIN" --outdir "$OUTDIR" --build-only read -r -a ARMS <<< "$ARMS_STR" PIDS=() cleanup() { local rc=$? trap - EXIT INT TERM for p in "${PIDS[@]:-}"; do [[ -n "$p" ]] || continue kill -TERM -- "-$p" 2>/dev/null || true done sleep 1 for p in "${PIDS[@]:-}"; do [[ -n "$p" ]] || continue kill -KILL -- "-$p" 2>/dev/null || true done pkill -f "[m]easure_melee_strong_field.*$OUTDIR" 2>/dev/null || true exit "$rc" } trap cleanup EXIT INT TERM for arm in "${ARMS[@]}"; do echo "[driver] launching arm $arm (runs=$RUNS rounds=$ROUNDS) field=$MELEE_FIELD" setsid "$BIN" --outdir "$OUTDIR" --arm "$arm" \ --run-start 1 --run-count "$RUNS" --rounds "$ROUNDS" \ > "$OUTDIR/$arm.driver.log" 2>&1 & PIDS+=("$!") sleep 3 # stagger so concurrent servers do not race for the same free port done echo "[driver] all arms launched; waiting…" FAIL=0 for p in "${PIDS[@]}"; do wait "$p" || FAIL=$((FAIL + 1)) done PIDS=() echo "[driver] done (failed arm processes: $FAIL)" exit "$FAIL"