164 lines
4.6 KiB
Bash
Executable file
164 lines
4.6 KiB
Bash
Executable file
#!/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 <<EOF
|
|
Usage: $0 {status|start|stop|restart}
|
|
|
|
Canonical WorldBox control for IdleSpectator.
|
|
Always invoke with unrestricted / all permissions (never from the Cursor sandbox).
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
local op="${1:-}"
|
|
case "$op" in
|
|
status) cmd_status ;;
|
|
start) cmd_start ;;
|
|
stop) cmd_stop ;;
|
|
restart) cmd_restart ;;
|
|
-h|--help|help|"") usage; exit 2 ;;
|
|
*)
|
|
echo "Unknown op: $op" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|