542 lines
19 KiB
Bash
542 lines
19 KiB
Bash
#!/usr/bin/env bash
|
|
# Dev commands: play, editor, lint, format, test, verify, screenshot
|
|
|
|
cmd_play() {
|
|
local LOG_FILE="$REPO_ROOT/.project/logs/game_$(date +%Y%m%d_%H%M%S).log"
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
echo -e "${BLUE}Launching Magic Civilization...${NC}"
|
|
echo -e "${BLUE}Log: $LOG_FILE${NC}"
|
|
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
|
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
|
$GODOT_BIN --path "$GAME_DIR" --rendering-method gl_compatibility "$@" 2>&1 | tee "$LOG_FILE"
|
|
local EXIT_CODE=${PIPESTATUS[0]}
|
|
if [ $EXIT_CODE -ne 0 ]; then
|
|
echo -e "\n${RED}Game exited with code $EXIT_CODE${NC}"
|
|
echo -e "${RED}Crash log: $LOG_FILE${NC}"
|
|
tail -20 "$LOG_FILE" | grep -E "SCRIPT ERROR|ERROR:|Crash|FATAL|at:" | head -10
|
|
fi
|
|
}
|
|
|
|
cmd_editor() {
|
|
echo -e "${BLUE}Opening Godot editor...${NC}"
|
|
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
|
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
|
$GODOT_BIN --path "$GAME_DIR" -e --rendering-method gl_compatibility "$@" &
|
|
}
|
|
|
|
cmd_validate() {
|
|
echo -e "${BLUE}Validating game data JSON schemas...${NC}"
|
|
python3 "$REPO_ROOT/tools/validate-game-data.py" "$@"
|
|
}
|
|
|
|
## ─── Optional-tool detection helper ─────────────────────────────────────
|
|
## Returns 0 if available; 1 + yellow warning if not.
|
|
_have_tool() {
|
|
local tool="$1"
|
|
local hint="${2:-}"
|
|
if command -v "$tool" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
if [ -n "$hint" ]; then
|
|
echo -e "${YELLOW}SKIP: '$tool' not installed — install with: $hint${NC}"
|
|
else
|
|
echo -e "${YELLOW}SKIP: '$tool' not installed${NC}"
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
## Apply project-local gdlintrc — load-bearing before every gdlint call.
|
|
## lilith-gdtoolkit-sync keeps resetting gdlintrc to defaults; this restores
|
|
## the project carveouts (GDExtension wrapper method counts, signal handler
|
|
## signatures, etc.). Also ensures the config-drift check is run first.
|
|
_gd_prep_lint() {
|
|
lilith-gdtoolkit-sync --check || {
|
|
echo -e "${YELLOW}Config drift detected — syncing...${NC}"
|
|
lilith-gdtoolkit-sync
|
|
}
|
|
cp "$REPO_ROOT/.project/gdlintrc.local" "$REPO_ROOT/gdlintrc" 2>/dev/null || true
|
|
}
|
|
|
|
## ─── Per-language lint splits ───────────────────────────────────────────
|
|
|
|
cmd_lint_gd() {
|
|
local exit_code=0
|
|
echo -e "${BLUE}GDScript lint (gdlint + gdformat --check)...${NC}"
|
|
_gd_prep_lint
|
|
gdlint "$GAME_DIR/engine/src/" || exit_code=$?
|
|
gdformat --check "$GAME_DIR/engine/src/" || exit_code=$?
|
|
return $exit_code
|
|
}
|
|
|
|
cmd_lint_rust() {
|
|
local exit_code=0
|
|
echo -e "${BLUE}Rust lint (fmt --check + clippy + machete)...${NC}"
|
|
(cd "$SIMULATOR_DIR" && cargo fmt --check --all) || exit_code=$?
|
|
(cd "$SIMULATOR_DIR" && cargo clippy --workspace --all-targets -- -D warnings) || exit_code=$?
|
|
if _have_tool cargo-machete "cargo install cargo-machete"; then
|
|
(cd "$SIMULATOR_DIR" && cargo machete) || exit_code=$?
|
|
fi
|
|
return $exit_code
|
|
}
|
|
|
|
cmd_lint_ts() {
|
|
local exit_code=0
|
|
echo -e "${BLUE}TypeScript lint (ESLint + tsc typecheck)...${NC}"
|
|
pnpm --prefix "$GUIDE_DIR" lint || exit_code=$?
|
|
pnpm -r typecheck || exit_code=$?
|
|
return $exit_code
|
|
}
|
|
|
|
cmd_lint() {
|
|
local exit_code=0
|
|
echo -e "${BLUE}[1/3] GDScript lint${NC}"
|
|
cmd_lint_gd || exit_code=$?
|
|
echo ""
|
|
echo -e "${BLUE}[2/3] Rust lint${NC}"
|
|
cmd_lint_rust || exit_code=$?
|
|
echo ""
|
|
echo -e "${BLUE}[3/3] TypeScript lint${NC}"
|
|
cmd_lint_ts || exit_code=$?
|
|
return $exit_code
|
|
}
|
|
|
|
## ─── Per-language format splits ─────────────────────────────────────────
|
|
|
|
cmd_format_gd() {
|
|
echo -e "${BLUE}GDScript format (gdformat)...${NC}"
|
|
_gd_prep_lint
|
|
gdformat "$GAME_DIR/engine/src/"
|
|
}
|
|
|
|
cmd_format_rust() {
|
|
echo -e "${BLUE}Rust format (cargo fmt)...${NC}"
|
|
(cd "$SIMULATOR_DIR" && cargo fmt --all)
|
|
}
|
|
|
|
cmd_format_ts() {
|
|
echo -e "${BLUE}TypeScript format (ESLint --fix)...${NC}"
|
|
pnpm --prefix "$GUIDE_DIR" lint:fix
|
|
}
|
|
|
|
cmd_format() {
|
|
echo -e "${BLUE}[1/3] GDScript format${NC}"
|
|
cmd_format_gd
|
|
echo ""
|
|
echo -e "${BLUE}[2/3] Rust format${NC}"
|
|
cmd_format_rust
|
|
echo ""
|
|
echo -e "${BLUE}[3/3] TypeScript format${NC}"
|
|
cmd_format_ts
|
|
}
|
|
|
|
cmd_typecheck() {
|
|
echo -e "${BLUE}TypeScript typecheck (pnpm -r typecheck)...${NC}"
|
|
pnpm -r typecheck
|
|
}
|
|
|
|
## Run Rust workspace tests, preferring nextest when available.
|
|
_cargo_test_workspace() {
|
|
if _have_tool cargo-nextest "cargo install cargo-nextest --locked"; then
|
|
(cd "$SIMULATOR_DIR" && cargo nextest run --workspace)
|
|
else
|
|
(cd "$SIMULATOR_DIR" && cargo test --workspace)
|
|
fi
|
|
}
|
|
|
|
cmd_test() {
|
|
local exit_code=0
|
|
|
|
echo -e "${BLUE}Running GUT tests (GDScript)...${NC}"
|
|
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
|
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
|
$GODOT_BIN --path "$GAME_DIR" --headless --script res://addons/gut/gut_cmdln.gd \
|
|
-gexit "$@" || exit_code=$?
|
|
|
|
echo ""
|
|
echo -e "${BLUE}Running Rust tests (simulator)...${NC}"
|
|
_cargo_test_workspace || exit_code=$?
|
|
|
|
echo ""
|
|
echo -e "${BLUE}Running vitest (guide)...${NC}"
|
|
pnpm --prefix "$GUIDE_DIR" test || exit_code=$?
|
|
|
|
echo ""
|
|
echo -e "${BLUE}Running stability test (20s game boot)...${NC}"
|
|
_run_stability_test || exit_code=$?
|
|
|
|
return $exit_code
|
|
}
|
|
|
|
_run_stability_test() {
|
|
# Boots the game → world_map, waits 20s, captures screenshot.
|
|
# If the game crashes before capture, exit code is non-zero.
|
|
local LOG="/tmp/stability_test_$$.log"
|
|
cmd_screenshot "stability_test" "world_map" "20" > "$LOG" 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
echo -e "${RED}FAIL: Game crashed during stability test${NC}"
|
|
grep -E "SCRIPT ERROR|ERROR:" "$LOG" | head -5
|
|
return 1
|
|
fi
|
|
if grep -q "Captured:" "$LOG"; then
|
|
echo -e "${GREEN}PASS: Game stable for 20s, screenshot captured${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${RED}FAIL: Game ran but no screenshot captured${NC}"
|
|
cat "$LOG" | tail -5
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cmd_verify() {
|
|
local -a step_names step_times step_results
|
|
local overall_exit=0
|
|
|
|
_verify_step() {
|
|
local step_num="$1"
|
|
local total="$2"
|
|
local label="$3"
|
|
shift 3
|
|
|
|
echo ""
|
|
echo -e "${BLUE}[${step_num}/${total}] ${label}${NC}"
|
|
|
|
local t_start
|
|
t_start=$(date +%s%N)
|
|
|
|
if ! "$@"; then
|
|
local t_end elapsed
|
|
t_end=$(date +%s%N)
|
|
elapsed=$(( (t_end - t_start) / 1000000 ))
|
|
step_names+=("$label")
|
|
step_times+=("${elapsed}ms")
|
|
step_results+=("FAIL")
|
|
echo ""
|
|
echo -e "${RED}ABORT: '${label}' failed after ${elapsed}ms${NC}"
|
|
_verify_summary
|
|
exit 1
|
|
fi
|
|
|
|
local t_end elapsed
|
|
t_end=$(date +%s%N)
|
|
elapsed=$(( (t_end - t_start) / 1000000 ))
|
|
step_names+=("$label")
|
|
step_times+=("${elapsed}ms")
|
|
step_results+=("PASS")
|
|
}
|
|
|
|
_verify_run_in_dir() {
|
|
local dir="$1"; shift
|
|
(cd "$dir" && "$@")
|
|
}
|
|
|
|
_verify_summary() {
|
|
echo ""
|
|
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
|
|
echo -e "${BLUE} Regression Gate Summary${NC}"
|
|
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
|
|
local i
|
|
for i in "${!step_names[@]}"; do
|
|
local result="${step_results[$i]}"
|
|
local color
|
|
if [ "$result" = "PASS" ]; then
|
|
color="$GREEN"
|
|
else
|
|
color="$RED"
|
|
fi
|
|
printf " %-40s %s%-4s%s %s\n" \
|
|
"${step_names[$i]}" \
|
|
"$color" "$result" "$NC" \
|
|
"${step_times[$i]}"
|
|
done
|
|
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
|
|
# Count pending steps not yet run
|
|
local n_pass=0 n_fail=0
|
|
for r in "${step_results[@]}"; do
|
|
if [ "$r" = "PASS" ]; then
|
|
n_pass=$(( n_pass + 1 ))
|
|
else
|
|
n_fail=$(( n_fail + 1 ))
|
|
fi
|
|
done
|
|
if [ "$n_fail" -eq 0 ]; then
|
|
echo -e " ${GREEN}All ${n_pass} checks passed${NC}"
|
|
else
|
|
echo -e " ${RED}${n_fail} check(s) failed, ${n_pass} passed${NC}"
|
|
fi
|
|
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
|
|
}
|
|
|
|
local TOTAL=14
|
|
|
|
# Step 0 — Game data schema validation
|
|
_verify_step 0 $TOTAL "game data JSON schemas" \
|
|
python3 "$REPO_ROOT/tools/validate-game-data.py"
|
|
|
|
# Step 1 — Objectives dashboard freshness
|
|
# Fails if .project/objectives/README.md is stale vs the per-objective
|
|
# frontmatter. Run `python3 tools/objectives-report.py` to regenerate.
|
|
_verify_step 1 $TOTAL "objectives dashboard up-to-date" \
|
|
python3 "$REPO_ROOT/tools/objectives-report.py" --check
|
|
|
|
# Step 2 — Rust build
|
|
_verify_step 2 $TOTAL "cargo build --workspace" \
|
|
_verify_run_in_dir "$SIMULATOR_DIR" cargo build --workspace
|
|
|
|
# Step 3 — Rust tests (prefer nextest)
|
|
_verify_step 3 $TOTAL "cargo test --workspace" \
|
|
_cargo_test_workspace
|
|
|
|
# Step 4 — Rust clippy
|
|
_verify_step 4 $TOTAL "cargo clippy --workspace -D warnings" \
|
|
_verify_run_in_dir "$SIMULATOR_DIR" cargo clippy --workspace -- -D warnings
|
|
|
|
# Step 5 — Rust dead-deps scan (optional: cargo-machete)
|
|
_verify_step 5 $TOTAL "cargo machete (dead deps)" \
|
|
_verify_machete
|
|
|
|
# Step 6 — Rust advisories + license check (optional: cargo-deny)
|
|
_verify_step 6 $TOTAL "cargo deny check" \
|
|
_verify_deny
|
|
|
|
# Step 7 — Rust docs build (warnings are hard errors)
|
|
_verify_step 7 $TOTAL "cargo doc --no-deps --workspace" \
|
|
_verify_run_in_dir "$SIMULATOR_DIR" \
|
|
env RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace
|
|
|
|
# Step 8 — 500-LOC hard cap across languages
|
|
_verify_step 8 $TOTAL "file-size 500-LOC cap (.rs/.gd/.ts)" \
|
|
_verify_file_size_cap
|
|
|
|
# Step 9 — TS workspace typecheck (pnpm -r)
|
|
_verify_step 9 $TOTAL "pnpm -r typecheck" \
|
|
pnpm -r typecheck
|
|
|
|
# Apply project-local gdlint config before linting.
|
|
# The lilith-gdtoolkit-sync tool keeps overwriting gdlintrc with defaults
|
|
# (max-public-methods: 20, no-else-return enabled, unused-argument enabled).
|
|
# Our project needs carveouts for GDExtension wrappers (99+ methods on
|
|
# DataLoader, city.gd bridge methods, etc.) and signal handler signatures.
|
|
# .project/gdlintrc.local is the source of truth — copy it over before lint.
|
|
cp "$REPO_ROOT/.project/gdlintrc.local" "$REPO_ROOT/gdlintrc" 2>/dev/null
|
|
|
|
# Step 10 — GDScript lint: engine/src/
|
|
_verify_step 10 $TOTAL "gdlint engine/src/" \
|
|
gdlint "$GAME_DIR/engine/src/"
|
|
|
|
# Step 11 — GDScript lint: scenes/tests/
|
|
_verify_step 11 $TOTAL "gdlint engine/scenes/tests/" \
|
|
gdlint "$GAME_DIR/engine/scenes/tests/"
|
|
|
|
# Step 12 — GDScript lint: tests/integration/
|
|
_verify_step 12 $TOTAL "gdlint engine/tests/integration/" \
|
|
gdlint "$GAME_DIR/engine/tests/integration/"
|
|
|
|
# Step 13 — Godot headless boot: GDExtension + script compilation
|
|
_verify_step 13 $TOTAL "godot headless boot (no script errors)" \
|
|
_godot_headless_boot
|
|
|
|
_verify_summary
|
|
return $overall_exit
|
|
}
|
|
|
|
## ─── Verify step helpers ────────────────────────────────────────────────
|
|
|
|
_verify_machete() {
|
|
## Skip with a warning if cargo-machete is not installed — task #1
|
|
## contract: gracefully degrade on machines missing optional tools.
|
|
if ! _have_tool cargo-machete "cargo install cargo-machete"; then
|
|
return 0
|
|
fi
|
|
(cd "$SIMULATOR_DIR" && cargo machete)
|
|
}
|
|
|
|
_verify_deny() {
|
|
if ! _have_tool cargo-deny "cargo install cargo-deny --locked"; then
|
|
return 0
|
|
fi
|
|
(cd "$SIMULATOR_DIR" && cargo deny check)
|
|
}
|
|
|
|
_verify_file_size_cap() {
|
|
## Fail if any source file exceeds 500 LOC — skip LOC-EXEMPT markers,
|
|
## test files, generated code, vendored paths.
|
|
## Scanned roots: src/simulator/**/*.rs, src/game/engine/src/**/*.gd,
|
|
## src/packages/**/*.ts, public/games/age-of-dwarves/guide/src/**/*.ts.
|
|
local -a roots=(
|
|
"$SIMULATOR_DIR:rs"
|
|
"$GAME_DIR/engine/src:gd"
|
|
"$REPO_ROOT/src/packages:ts"
|
|
"$GUIDE_DIR/src:ts"
|
|
)
|
|
local violations=0
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
local spec root ext
|
|
for spec in "${roots[@]}"; do
|
|
root="${spec%:*}"
|
|
ext="${spec##*:}"
|
|
[ -d "$root" ] || continue
|
|
find "$root" \
|
|
-type d \( \
|
|
-name target -o -name node_modules -o -name dist -o \
|
|
-name build -o -name .local -o -name pkg -o -name coverage \
|
|
\) -prune -o \
|
|
-type f -name "*.${ext}" ! -name "*.test.ts" ! -name "*.spec.ts" \
|
|
! -name "*.generated.ts" ! -name "*.d.ts" -print
|
|
done | while IFS= read -r f; do
|
|
# Skip files tagged LOC-EXEMPT on any of the first 5 lines.
|
|
if head -n 5 "$f" 2>/dev/null | grep -q "LOC-EXEMPT"; then
|
|
continue
|
|
fi
|
|
local lines
|
|
lines=$(wc -l < "$f" | tr -d ' ')
|
|
if [ "$lines" -gt 500 ]; then
|
|
printf '%6d %s\n' "$lines" "$f" >> "$tmp"
|
|
fi
|
|
done
|
|
if [ -s "$tmp" ]; then
|
|
violations=$(wc -l < "$tmp" | tr -d ' ')
|
|
echo -e "${RED}Files exceeding 500-LOC cap (${violations}):${NC}"
|
|
cat "$tmp"
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
rm -f "$tmp"
|
|
return 0
|
|
}
|
|
|
|
cmd_coverage() {
|
|
## Generate coverage reports for Rust + TypeScript.
|
|
## Graceful degradation: each tool warn-skips if not installed.
|
|
local exit_code=0
|
|
|
|
echo -e "${BLUE}[1/2] Rust coverage (cargo llvm-cov)...${NC}"
|
|
if _have_tool cargo-llvm-cov "cargo install cargo-llvm-cov --locked"; then
|
|
(cd "$SIMULATOR_DIR" && cargo llvm-cov --workspace --html) || exit_code=$?
|
|
echo -e "${BLUE}HTML report: $SIMULATOR_DIR/target/llvm-cov/html/index.html${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BLUE}[2/2] TypeScript coverage (pnpm -r test:coverage)...${NC}"
|
|
## --if-present: pnpm exits 0 when no package defines the script, which is
|
|
## the graceful-degrade behavior we want. Without it, pnpm exits 1 with
|
|
## ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, which would falsely fail verify.
|
|
pnpm -r --if-present run test:coverage || exit_code=$?
|
|
|
|
return $exit_code
|
|
}
|
|
|
|
_godot_headless_boot() {
|
|
## Boot Godot headless and check for SCRIPT ERRORs.
|
|
## Catches class_name resolution failures, GDExtension load failures,
|
|
## and any other compile-time GDScript errors that gdlint cannot detect.
|
|
local log="/tmp/godot_headless_boot_$$.log"
|
|
$GODOT_BIN --path "$GAME_DIR" --rendering-method gl_compatibility --headless --quit 2>&1 | tee "$log"
|
|
local errors
|
|
errors=$(grep -cE "SCRIPT ERROR|^ERROR:" "$log" 2>/dev/null || true)
|
|
errors="${errors:-0}"
|
|
rm -f "$log"
|
|
if [ "$errors" -gt 0 ]; then
|
|
echo -e "${RED}Found $errors script/load errors in headless boot${NC}"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
cmd_screenshot() {
|
|
"$REPO_ROOT/tools/screenshot.sh" "$@"
|
|
}
|
|
|
|
cmd_guide() {
|
|
echo -e "${BLUE}Starting guide dev server (port 5800)...${NC}"
|
|
pnpm --prefix "$GUIDE_DIR" dev
|
|
}
|
|
|
|
cmd_test_golden() {
|
|
## Cross-language golden-vector parity gate.
|
|
##
|
|
## Each fixture in src/simulator/tests/golden/vectors/*.json is consumed by
|
|
## three runners that MUST produce bitwise-identical output. Divergence =
|
|
## release blocker (FFI marshaling / non-determinism / SOT violation).
|
|
##
|
|
## See src/simulator/tests/golden/README.md for the fixture shape and
|
|
## ~/.claude/instructions/rust-code-standards.md §"Testing Strategy" for rationale.
|
|
|
|
local vectors_dir="$SIMULATOR_DIR/tests/golden/vectors"
|
|
local exit_code=0
|
|
|
|
if [ ! -d "$vectors_dir" ]; then
|
|
echo -e "${RED}Golden vectors directory missing: $vectors_dir${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local vectors
|
|
vectors=$(find "$vectors_dir" -maxdepth 1 -name '*.json' -type f | sort)
|
|
|
|
if [ -z "$vectors" ]; then
|
|
echo -e "${YELLOW}No golden vectors yet — add JSON fixtures to:${NC}"
|
|
echo -e " $vectors_dir"
|
|
echo -e "${YELLOW}See $SIMULATOR_DIR/tests/golden/README.md for the fixture shape.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
local count
|
|
count=$(echo "$vectors" | wc -l | tr -d ' ')
|
|
echo -e "${BLUE}Found $count golden vector(s) — running 3-consumer parity check${NC}"
|
|
echo ""
|
|
|
|
# Consumer 1: Rust native
|
|
echo -e "${BLUE}[1/3] Rust native consumer (cargo test --test golden)${NC}"
|
|
if ! (cd "$SIMULATOR_DIR" && cargo test --workspace --test golden 2>&1); then
|
|
echo -e "${RED}FAIL: Rust golden tests${NC}"
|
|
exit_code=1
|
|
fi
|
|
echo ""
|
|
|
|
# Consumer 2: WASM via Vitest (guide simulation worker)
|
|
echo -e "${BLUE}[2/3] WASM consumer (pnpm test — golden suite)${NC}"
|
|
if ! pnpm --prefix "$GUIDE_DIR" test -- --run golden 2>&1; then
|
|
echo -e "${RED}FAIL: WASM golden tests${NC}"
|
|
exit_code=1
|
|
fi
|
|
echo ""
|
|
|
|
# Consumer 3: GDExtension via headless Godot + GUT
|
|
echo -e "${BLUE}[3/3] GDExtension consumer (headless Godot + GUT ffi/)${NC}"
|
|
local ffi_dir="$GAME_DIR/engine/tests/ffi"
|
|
if [ -d "$ffi_dir" ] && [ -n "$(find "$ffi_dir" -maxdepth 1 -name 'test_golden_*.gd' -print -quit 2>/dev/null)" ]; then
|
|
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
|
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
|
$GODOT_BIN --path "$GAME_DIR" --headless \
|
|
--script res://addons/gut/gut_cmdln.gd \
|
|
-gdir=res://engine/tests/ffi -gprefix=test_golden_ -gexit 2>&1 \
|
|
|| exit_code=$?
|
|
else
|
|
echo -e "${YELLOW}SKIP: No GDExt golden tests yet at $ffi_dir/test_golden_*.gd${NC}"
|
|
fi
|
|
echo ""
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
echo -e "${GREEN}All 3 consumers agree on $count vector(s)${NC}"
|
|
else
|
|
echo -e "${RED}Divergence detected — release blocker${NC}"
|
|
echo -e "${RED}See src/simulator/tests/golden/README.md for triage guidance${NC}"
|
|
fi
|
|
return $exit_code
|
|
}
|
|
|
|
cmd_autoplay() {
|
|
# Single-seed fast feedback: ./run autoplay [seed]
|
|
local seed="${1:-1}"
|
|
local results_dir="/tmp/autoplay_single_${seed}"
|
|
bash "$(dirname "${BASH_SOURCE[0]}")/../../tools/autoplay-batch.sh" 1 500 "$results_dir" || return $?
|
|
python3 "$(dirname "${BASH_SOURCE[0]}")/../../tools/autoplay-report.py" "$results_dir"
|
|
}
|
|
|
|
cmd_autoplay_batch() {
|
|
# Multi-seed regression gate: ./run autoplay-batch [count]
|
|
local count="${1:-3}"
|
|
local results_dir="/tmp/autoplay_batch_$(date +%s)"
|
|
bash "$(dirname "${BASH_SOURCE[0]}")/../../tools/autoplay-batch.sh" "$count" 500 "$results_dir" || return $?
|
|
python3 "$(dirname "${BASH_SOURCE[0]}")/../../tools/autoplay-report.py" "$results_dir"
|
|
}
|