#!/usr/bin/env bash # batch-outcomes.sh — Summarize per-seed outcomes in a batch directory. # # Usage: # tools/batch-outcomes.sh # tools/batch-outcomes.sh apricot: # runs over ssh # # Output (one line per seed, tab-separated): # seedN outcome turn p0_cities p1_cities wall_clock_sec # # The batch_dir must contain one or more `game_*/turn_stats.jsonl` files. # Works for smoke/clan single-mode batches. For gpu-walltime and other # multi-mode batches, run once per mode subdir: # tools/batch-outcomes.sh apricot:~/.cache/mc-batches/20260418_080214/gpu-true set -euo pipefail TARGET="${1:?usage: tools/batch-outcomes.sh }" # The query is identical local vs remote — just the execution host differs. # Packing it as a here-doc keeps escaping sane. read -r -d '' QUERY <<'EOF' || true set -e : "${DIR:?DIR must be set}" for d in "$DIR"/game_*; do [ -d "$d" ] || continue seed=$(basename "$d" | grep -oE 'seed[0-9]+') stats="$d/turn_stats.jsonl" [ -s "$stats" ] || { printf '%s\tNO-STATS\t-\t-\t-\t-\n' "$seed"; continue; } last=$(tail -1 "$stats") python3 - "$last" "$seed" <<'PY' import json, sys line, seed = sys.argv[1], sys.argv[2] try: d = json.loads(line) except Exception as e: print(f"{seed}\tPARSE-ERR\t-\t-\t-\t-") sys.exit(0) outcome = d.get("outcome", "?") turn = d.get("turn", "?") wc = d.get("wall_clock_sec", "?") ps = d.get("player_stats", {}) p0c = ps.get("0", {}).get("cities", "-") p1c = ps.get("1", {}).get("cities", "-") wcs = f"{wc:.1f}" if isinstance(wc, (int, float)) else str(wc) print(f"{seed}\t{outcome}\t{turn}\t{p0c}\t{p1c}\t{wcs}") PY done | sort -V EOF if [[ "$TARGET" == apricot:* ]]; then REMOTE_PATH="${TARGET#apricot:}" ssh apricot "DIR='${REMOTE_PATH}' bash -s" <<< "$QUERY" else DIR="$TARGET" bash -c "$QUERY" fi