magicciv/scripts/player-api-server.sh

91 lines
3.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Generic external-player JSON-Lines harness for Magic Civilization.
#
# Spawns Godot headless with the `player_api_main` scene and pipes
# stdin/stdout. Any client that speaks the JSON-Lines wire protocol
# documented in `src/game/engine/docs/PLAYER_API.md` can drive a game
# slot through this harness — Claude Code via the
# `tooling/claude-player-mcp/` adapter, an OpenSpiel/RL trainer via a
# Python `subprocess.Popen`, a smoke-test shell script, etc.
#
# Env vars (see PLAYER_API.md for the full schema):
# CP_SEED, CP_PLAYERS, CP_PLAYER_SLOT, CP_MAP_SIZE, CP_MAP_TYPE,
# CP_OMNISCIENT, CP_TIMEOUT_SEC, CP_LOG_FILE.
#
# `CP_PLAYER_SLOT` is the env-var name the harness has used since p2-67;
# it identifies the externally-controlled player slot — kept as-is for
# backward compatibility with existing clients. Despite the name it is
# not Claude-specific.
#
# Exit code 0 on clean shutdown (shutdown request received or stdin EOF).
# Non-zero on protocol error or harness crash.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Wrap godot in heavy-tests.slice on Linux so a fleet of player_api workers
# (typical RL training spawns 16-64) cannot starve sshd / interactive work.
# See scripts/run/heavy-prefix.sh and ~/.config/systemd/user/heavy-tests.slice.
# shellcheck source=run/heavy-prefix.sh
source "${SCRIPT_DIR}/run/heavy-prefix.sh"
# Defaults — adapter overrides via env.
: "${CP_SEED:=42}"
: "${CP_PLAYERS:=2}"
: "${CP_PLAYER_SLOT:=0}"
: "${CP_MAP_SIZE:=duel}"
: "${CP_MAP_TYPE:=continents}"
: "${CP_OMNISCIENT:=0}"
: "${CP_TIMEOUT_SEC:=60}"
: "${CP_LOG_FILE:=}"
# Pure-headless mode — no Wayland needed. JSON-Lines speaks to stdout.
#
# Linux uses flatpak Godot (matches the rest of the apricot pipeline).
# macOS uses the locally-installed `godot` binary (Homebrew); a parallel
# flatpak runtime just for this harness is silly when native Godot 4
# works directly. Env-var passthrough is automatic for the native path.
export CP_SEED CP_PLAYERS CP_PLAYER_SLOT CP_MAP_SIZE CP_MAP_TYPE \
CP_OMNISCIENT CP_TIMEOUT_SEC CP_LOG_FILE CP_VICTORY_MODE \
CP_PLAYER_CONTROLLERS CP_PLAYER_SLOTS \
MC_DATA_ROOT MC_LEARNED_POLICY_PATH
case "$(uname -s)" in
Darwin)
GODOT_BIN="${GODOT_BIN:-godot}"
if ! command -v "$GODOT_BIN" >/dev/null 2>&1; then
echo "ERROR: no godot binary on PATH (set GODOT_BIN or 'brew install godot')." >&2
exit 1
fi
exec "$GODOT_BIN" \
--path "$PROJECT_DIR/src/game" \
--headless \
--rendering-method gl_compatibility \
res://engine/scenes/headless/player_api_main.tscn
;;
*)
heavy_exec "player-api-$$" \
flatpak run --user \
--env=CP_SEED="$CP_SEED" \
--env=CP_PLAYERS="$CP_PLAYERS" \
--env=CP_PLAYER_SLOT="$CP_PLAYER_SLOT" \
--env=CP_MAP_SIZE="$CP_MAP_SIZE" \
--env=CP_MAP_TYPE="$CP_MAP_TYPE" \
--env=CP_OMNISCIENT="$CP_OMNISCIENT" \
--env=CP_TIMEOUT_SEC="$CP_TIMEOUT_SEC" \
--env=CP_LOG_FILE="$CP_LOG_FILE" \
--env=CP_VICTORY_MODE="${CP_VICTORY_MODE:-}" \
--env=CP_PLAYER_CONTROLLERS="${CP_PLAYER_CONTROLLERS:-}" \
--env=CP_PLAYER_SLOTS="${CP_PLAYER_SLOTS:-}" \
--env=MC_DATA_ROOT="${MC_DATA_ROOT:-}" \
--env=MC_LEARNED_POLICY_PATH="${MC_LEARNED_POLICY_PATH:-}" \
org.godotengine.Godot \
--path "$PROJECT_DIR/src/game" \
--headless \
--rendering-method gl_compatibility \
res://engine/scenes/headless/player_api_main.tscn
;;
esac