#!/usr/bin/env bash # Canonical WorldBox process control for IdleSpectator agents. # Always run this script (or harness-run.sh) with unrestricted / `all` permissions. # Never invent ad-hoc pkill -f patterns that include the worldbox path in the # agent shell command line - that can kill the agent itself. # # Usage: # ./scripts/worldbox-ctl.sh status # ./scripts/worldbox-ctl.sh start # ./scripts/worldbox-ctl.sh stop # ./scripts/worldbox-ctl.sh restart # stop + start + wait for IdleSpectator ready set -euo pipefail STEAM_APP_ID="${WORLDBOX_APP_ID:-1206560}" LOG="${WORLDBOX_LOG:-$HOME/.config/unity3d/mkarpenko/WorldBox/Player.log}" BOOT_TIMEOUT_SEC="${HARNESS_BOOT_TIMEOUT:-180}" LAUNCH_LOG="${WORLDBOX_LAUNCH_LOG:-/tmp/idle-spectator-harness-launch.log}" worldbox_pids() { # Match the real game binary only (process name "worldbox"). # Do NOT use pkill -f with a path string that also appears in the calling shell. ps -C worldbox -o pid= 2>/dev/null | tr -s ' ' | sed '/^$/d' || true } worldbox_running() { local pids pids="$(worldbox_pids)" [[ -n "${pids// /}" ]] } mod_ready() { [[ -f "$LOG" ]] || return 1 # Compile errors (NML prints error CS during Compile Mod IdleSpectator). if rg -q "error CS" "$LOG" 2>/dev/null \ && rg -q "Compile Mod IdleSpectator|IdleSpectator.*error CS|error CS.*IdleSpectator" "$LOG" 2>/dev/null; then return 2 fi # Harmony PatchAll failures disable the mod without a CS error. if rg -q "IdleSpectator has been disabled due to an error" "$LOG" 2>/dev/null \ || rg -q "\[IdleSpectator\]: Harmony failed" "$LOG" 2>/dev/null; then return 3 fi rg -q "\[IdleSpectator\]: Harmony patches applied" "$LOG" 2>/dev/null } cmd_status() { if worldbox_running; then echo "WorldBox running pids=$(worldbox_pids | tr '\n' ' ')" if mod_ready; then echo "IdleSpectator: ready (Harmony patches applied)" else echo "IdleSpectator: not ready yet (check Player.log)" fi return 0 fi echo "WorldBox not running" return 1 } cmd_stop() { if ! worldbox_running; then echo "WorldBox already stopped" return 0 fi echo "Stopping WorldBox ..." # killall by process name - safe for agent shells (no -f path match). killall -q worldbox 2>/dev/null || true local i for i in $(seq 1 30); do if ! worldbox_running; then echo "WorldBox stopped" return 0 fi sleep 0.5 done echo "WorldBox still running after killall; sending SIGKILL ..." >&2 killall -q -9 worldbox 2>/dev/null || true sleep 1 if worldbox_running; then echo "FAILED to stop WorldBox pids=$(worldbox_pids | tr '\n' ' ')" >&2 return 1 fi echo "WorldBox stopped" } cmd_start() { if worldbox_running; then echo "WorldBox already running" return 0 fi echo "Starting WorldBox (steam app $STEAM_APP_ID) ..." echo "===== WORLDBOX-CTL START $(date -Iseconds) =====" >>"$LAUNCH_LOG" 2>/dev/null || true nohup steam -applaunch "$STEAM_APP_ID" >>"$LAUNCH_LOG" 2>&1 & disown || true local deadline=$((SECONDS + BOOT_TIMEOUT_SEC)) local saw=0 while (( SECONDS < deadline )); do if worldbox_running; then saw=1 local ready_rc=0 mod_ready || ready_rc=$? if (( ready_rc == 0 )); then echo "WorldBox + IdleSpectator ready" return 0 fi if (( ready_rc == 2 )); then echo "IdleSpectator compile failed - see $LOG" >&2 rg -n "error CS|Compile Mod IdleSpectator" "$LOG" 2>/dev/null | tail -n 40 >&2 || true return 1 fi if (( ready_rc == 3 )); then echo "IdleSpectator Harmony failed - see $LOG" >&2 rg -n "Harmony failed|Parameter \"|IdleSpectator has been disabled|IL Compile Error" "$LOG" 2>/dev/null | tail -n 40 >&2 || true return 1 fi printf '.' else printf 'o' fi sleep 2 done echo >&2 if (( saw == 0 )); then echo "Timed out waiting for WorldBox process (${BOOT_TIMEOUT_SEC}s)" >&2 else echo "Timed out waiting for IdleSpectator ready (${BOOT_TIMEOUT_SEC}s)" >&2 fi tail -n 40 "$LOG" 2>/dev/null >&2 || true return 1 } cmd_restart() { cmd_stop sleep 2 cmd_start } usage() { cat <&2 usage exit 2 ;; esac } main "$@"