#!/usr/bin/env bash # ss — One-shot proof-scene screenshot capture. # # Usage: # ./ss # run scene locally (flatpak Godot on this host) # ./ss --remote # rsync → ssh → weston+Godot → scp PNG back # ./ss --remote [scene_path_override] # # `` resolves a scene under `src/game/engine/scenes/tests/`: # 1. `proof_.tscn` (preferred — matches the project's proof-scene convention) # 2. `.tscn` (fallback) # # The chosen scene is launched as the main scene; it must self-capture # its viewport into `user://screenshots/.png`. (See proof scenes # under `src/game/engine/scenes/tests/` for the pattern — they read # `OS.get_environment("SCREENSHOT_NAME")` and call `Image.save_png`.) # # Output (local, after success): `screenshots/.png` at repo root. # `--remote` mode pulls the PNG back over scp. # # Why this wrapper exists: # `tools/screenshot.sh` only knows two scenes (main_menu, game_setup) via # the `capture_screenshot.gd` autoload. Proof scenes self-capture, so we # bypass the autoload and launch the scene directly. `./ss --remote` is # the path for mac-edit / apricot-run developers — the host check below # prevents accidentally trying to flatpak-launch on macOS. set -uo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GODOT_PROJECT_NAME="Magic Civilization" SCENES_REL_DIR="src/game/engine/scenes/tests" REMOTE="" NAME="" SCENE_OVERRIDE="" while [[ $# -gt 0 ]]; do case "$1" in --remote) REMOTE="${2:-}" if [[ -z "$REMOTE" ]]; then echo "ss: --remote needs a host argument" >&2 exit 2 fi shift 2 ;; -h|--help) awk '/^# / { sub(/^# ?/, ""); print; next } NR>1 { exit }' "${BASH_SOURCE[0]}" exit 0 ;; *) if [[ -z "$NAME" ]]; then NAME="$1" elif [[ -z "$SCENE_OVERRIDE" ]]; then SCENE_OVERRIDE="$1" else echo "ss: unexpected positional argument: $1" >&2 exit 2 fi shift ;; esac done if [[ -z "$NAME" ]]; then echo "Usage: ./ss [--remote ] [scene_path_override]" >&2 exit 2 fi # Resolve scene path (relative to repo root). if [[ -n "$SCENE_OVERRIDE" ]]; then SCENE_REL="$SCENE_OVERRIDE" elif [[ -f "$REPO_ROOT/$SCENES_REL_DIR/proof_$NAME.tscn" ]]; then SCENE_REL="$SCENES_REL_DIR/proof_$NAME.tscn" elif [[ -f "$REPO_ROOT/$SCENES_REL_DIR/$NAME.tscn" ]]; then SCENE_REL="$SCENES_REL_DIR/$NAME.tscn" else echo "ss: scene not found — tried:" >&2 echo " $SCENES_REL_DIR/proof_$NAME.tscn" >&2 echo " $SCENES_REL_DIR/$NAME.tscn" >&2 echo "Pass a path explicitly: ./ss [--remote ] $NAME " >&2 exit 1 fi if [[ ! -f "$REPO_ROOT/$SCENE_REL" ]]; then echo "ss: scene file does not exist: $SCENE_REL" >&2 exit 1 fi # `res://` paths are relative to `src/game/`, not the repo root. SCENE_RES="res://${SCENE_REL#src/game/}" LOCAL_OUT_DIR="$REPO_ROOT/screenshots" LOCAL_OUT="$LOCAL_OUT_DIR/$NAME.png" mkdir -p "$LOCAL_OUT_DIR" run_remote() { local host="$1" local remote_repo remote_repo="$(ssh -o ConnectTimeout=10 "$host" 'echo $HOME/Code/@projects/@magic-civilization')" || { echo "ss: cannot reach $host" >&2 return 1 } echo "ss: rsync → $host:$remote_repo" rsync -az \ --exclude='target/' --exclude='pkg/' --exclude='.local/' \ --exclude='addons/*/*.so' --exclude='addons/*/*.dylib' --exclude='addons/*/*.dll' \ --exclude='node_modules/' --exclude='.git/' --exclude='screenshots/' \ "$REPO_ROOT/" "$host:$remote_repo/" >/dev/null # Run scene under weston (virtual wayland → software-rendering Godot → # viewport.get_texture().get_image() works under llvmpipe). # The proof scene self-captures to user://screenshots/.png and quits. local remote_script remote_script=$(cat </tmp/ss-weston.log 2>&1 & WESTON_PID=\$! trap 'kill \$WESTON_PID 2>/dev/null || true' EXIT sleep 1 timeout 90 flatpak run --user \ --filesystem=home \ --socket=wayland \ --env=WAYLAND_DISPLAY="\$SOCKET" \ --env=SCREENSHOT_NAME="$NAME" \ --filesystem="xdg-run/\$SOCKET" \ org.godotengine.Godot \ --path . \ --rendering-method gl_compatibility \ --quit-after 200 \ "$SCENE_RES" 2>&1 | tail -40 echo "ss: scene exit code \$?" REMOTE_EOF ) ssh "$host" bash -s <<< "$remote_script" # The proof scene self-captures to user://screenshots/.png AND the # capture_screenshot.gd autoload also fires (it triggers on SCREENSHOT_NAME), # writing user://screenshots/_.png. Both end up under # the flatpak userdata dir; we resolve the newest match by globbing both # patterns on the remote and copying the most recent. # Quote the project name on the remote side so the embedded space in # "Magic Civilization" survives `ssh "$host" ""`. local remote_glob_cmd printf -v remote_glob_cmd 'ls -t "$HOME/.var/app/org.godotengine.Godot/data/godot/app_userdata/%s/screenshots/"%s*.png 2>/dev/null | head -1' \ "$GODOT_PROJECT_NAME" "$NAME" local remote_png remote_png="$(ssh "$host" "$remote_glob_cmd")" if [[ -z "$remote_png" ]]; then echo "ss: no PNG matching ${NAME}*.png on $host. Check /tmp/ss-weston.log on $host and the Godot output above." >&2 return 1 fi if scp "$host:$remote_png" "$LOCAL_OUT" 2>/dev/null; then echo "ss: captured → $LOCAL_OUT (from $remote_png)" return 0 fi echo "ss: scp failed for $remote_png" >&2 return 1 } run_local() { if ! command -v flatpak >/dev/null 2>&1; then echo "ss: local mode requires flatpak Godot. On macOS, use: ./ss --remote apricot $NAME" >&2 return 2 fi cd "$REPO_ROOT/src/game" timeout 90 flatpak run --user \ --env=SCREENSHOT_NAME="$NAME" \ org.godotengine.Godot \ --path . \ --rendering-method gl_compatibility \ --quit-after 200 \ "$SCENE_RES" 2>&1 | tail -40 local userdata_png="$HOME/.var/app/org.godotengine.Godot/data/godot/app_userdata/$GODOT_PROJECT_NAME/screenshots/$NAME.png" if [[ -f "$userdata_png" ]]; then cp "$userdata_png" "$LOCAL_OUT" echo "ss: captured → $LOCAL_OUT" return 0 fi echo "ss: screenshot not written at $userdata_png" >&2 return 1 } if [[ -n "$REMOTE" ]]; then run_remote "$REMOTE" else run_local fi