GODOT := $(shell command -v godot4 2>/dev/null || command -v godot 2>/dev/null) .PHONY: help setup build client server game atlas stop test test-tooling lint lint-python setup-venv ci ci-client ci-server clean \ decisions-sync decisions-active decisions-validate \ validate-content check-fact-ids setup-hooks install-hooks \ audit deny atlas-verify economy-db regen-db check-systems-db \ pre-pr pre-pr-lint pre-pr-build pre-pr-test pre-pr-validate pre-pr-fixtures \ pre-pr-server pre-pr-client pre-pr-content \ fixtures-client fixtures-gauntlet golden-diff golden-update \ checklist-validate checklist-generate check-star-map star-map-data \ build-sr-voice run-sr-voice test-voice-mock test-voice-real \ perf-baseline debug-schedule \ test-ipc-fixtures test-ipc-protocol test-ipc-integration test-ipc-benchmark \ screenshot visual-movie test-visual visual-update \ manifest # --- Configuration --- GODOT_VERSION ?= 4.6 # Default help: @echo "The Settled Reach — Development Commands" @echo "" @echo " make setup Install dev dependencies (Rust, Godot, tooling, venv)" @echo " make setup-venv Create .venv and install Python tooling deps" @echo " make build Build client and server" @echo " make game Build and run the full game (server + client)" @echo " make atlas Standalone Atlas companion app — attach or spawn, read-only (D-254)" @echo " make stop Stop any running server instance" @echo " make client Run the Godot client (test mode)" @echo " make server Run the Rust simulation server" @echo " make test Run all tests" @echo " make test-tooling Tooling test gate: sim determinism + economics dry-run" @echo " make test-ipc-fixtures Layer 1: IPC serialization fixtures" @echo " make test-ipc-protocol Layer 2: mock IPC protocol tests" @echo " make test-ipc-integration Layer 3: real subprocess round-trip" @echo " make test-ipc-benchmark IPC latency benchmark (blocked: #555/#556)" @echo " make lint Run all linters (server, client, python)" @echo " make lint-python Run ruff on tooling/" @echo " make ci Run full CI pipeline locally" @echo " make ci-client Run client CI checks" @echo " make ci-server Run server CI checks" @echo " make clean Remove build artifacts and caches" @echo "" @echo " make decisions-sync Sync governance/*.md decision records into pql.db" @echo " make decisions-active List active confirmed decisions (pql)" @echo " make decisions-validate Validate decision records — malformed-record gate (pql)" @echo " make audit Run cargo audit (security advisory check)" @echo " make deny Run cargo deny check (license/ban policy)" @echo " make validate-content Validate content YAML against schemas" @echo " make check-fact-ids Check fact_id references against knowledge catalogs" @echo " make atlas-verify Verify atlas proposal JSONs (all in docs/atlas/proposals/)" @echo " make star-map-data Regenerate client/data/star_map_data.json from systems.db + wiki" @echo " make check-star-map Assert star_map_data.json is up to date (part of pre-pr-client)" @echo " make economy-db Import economics data into systems.db (TOML/JSON → SQLite)" @echo " make regen-db Regenerate systems.db from all sources + stamp meta table (#855)" @echo " make check-systems-db Verify systems.db meta stamp matches current generator sources" @echo " make install-hooks Install pre-push + pre-commit git hooks (once per clone)" @echo " make fixtures-client Generate GDScript->Rust cross-encoder fixtures (#475)" @echo " make golden-diff Show diff if golden file output has changed" @echo " make golden-update Regenerate golden file and stage for commit" @echo " make checklist-validate Validate checklist YAML against schema" @echo " make checklist-generate Validate checklists + print condition summary" @echo " make perf-baseline Run performance benchmarks and save baseline" @echo "" @echo " make screenshot Ad-hoc visual capture (SCENARIO=name, default: fog_3state)" @echo " make visual-movie Flow capture with contact sheet (FLOW=name)" @echo " make test-visual Run visual golden regression tests" @echo " make visual-update Regenerate visual goldens and stage for commit" @echo "" @echo " make manifest Regenerate assets/characters/manifest.json from asset dirs (#720)" @echo " make pre-pr Run all pre-PR checks (lint, build, test, validate, fixtures)" @echo " make pre-pr-server Server-scoped pre-PR (lint, build, test, fixtures)" @echo " make pre-pr-client Client-scoped pre-PR (lint, build, test)" @echo " make pre-pr-content Content-scoped pre-PR (schema + cross-ref validation)" @echo "" @echo " make setup-hooks Install pre-commit hooks (included in setup)" @echo " make build-sr-voice Build sr-voice LLM inference service" @echo " make serve-sr-voice Start sr-voice server (ARGS='--model ')" @echo " make run-sr-voice Submit to sr-voice server (ARGS='generate|batch|benchmark ...')" @echo " make stop-sr-voice Stop sr-voice server" @echo " make test-voice-mock Test voice pipeline with mock sr-voice" @echo " make test-voice-real Test voice pipeline with real sr-voice + Gemma 2B" @echo " make debug-schedule Print bevy_ecs schedule graph (diff for PR artifacts)" @echo "" @echo " GODOT_VERSION=4.6 make setup Override Godot version" # --- Setup --- setup: setup-rust setup-godot setup-tooling setup-venv setup-hooks decisions-sync @echo "Dev environment ready." setup-rust: @tooling/install-rust setup-godot: @GODOT_VERSION=$(GODOT_VERSION) tooling/install-godot setup-tooling: @echo "Checking tooling dependencies..." @command -v python3 >/dev/null 2>&1 || { echo "Install Python 3"; exit 1; } @command -v curl >/dev/null 2>&1 || { echo "Install curl (required for Godot download)"; exit 1; } @command -v unzip >/dev/null 2>&1 || { echo "Install unzip (required for Godot download)"; exit 1; } @python3 -c "import yaml" 2>/dev/null || { echo "Install PyYAML: pip install pyyaml"; exit 1; } @python3 -c "import jsonschema" 2>/dev/null || { echo "Install jsonschema: pip install jsonschema"; exit 1; } setup-hooks: @git config core.hooksPath .config/hooks @echo "Git hooks path set to .config/hooks" install-hooks: setup-hooks @chmod +x .config/hooks/pre-push .config/hooks/pre-commit \ .config/hooks/post-merge .config/hooks/post-checkout .config/hooks/post-rewrite @echo "Hooks installed — pre-commit, pre-push, post-merge, post-checkout, post-rewrite are active." setup-venv: @python3 -m venv .venv @.venv/bin/pip install -e ".[dev]" --quiet @echo "Venv ready at .venv — activate with: source .venv/bin/activate" # --- Build --- build: build-server build-client build-server: cd server && cargo build build-server-release: cd server && cargo build --release build-client: @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } @# First import may error on theme/font loading before the import scan completes. @# Run twice: first pass generates imports silently, second pass validates clean. @$(GODOT) --headless --path client --import --quit 2>/dev/null || true $(GODOT) --headless --path client --import --quit # --- Run --- server: cd server && cargo run --bin settled-reach-server client: @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } $(GODOT) --path client game: stop build @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } @echo "Starting server..." @cd server && cargo run --bin settled-reach-server & @sleep 2 @echo "Starting client..." @SR_LIVE=1 $(GODOT) --path client @$(MAKE) stop # D-254/T-1132: standalone Atlas companion app. Unlike `game`, no orchestration # here — atlas_standalone.gd owns its own attach-or-spawn decision and child # server lifecycle internally (D-254 §1/§3), so this target is just "build, # then launch the standalone scene". build-server ensures the debug binary # exists for spawn-mode (atlas_standalone.gd resolves it at # server/target/debug/settled-reach-server, the same path the E2E test files # use); build-client ensures imports are current. SR_LIVE=1 is required — # without it SimBridge boots in test mode (instant fake CONNECTED, no network # at all), which would make the companion open the Atlas against a dynamic # test-harness snapshot instead of the real systems.db world via the wire. # Release server for the atlas: a cold DEBUG server takes >10s before the # first tile of a new body can exist (first-body terrain analysis), which # reads as a black/broken map on entry (live-verified 2026-07-22). The # release binary serves a cold body's first tiles in well under a second. # SR_SERVER_BIN tells atlas_standalone.gd's spawn path which binary to use. atlas: build-server-release build-client @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } @SR_LIVE=1 SR_SERVER_BIN=server/target/release/settled-reach-server $(GODOT) --path client scenes/atlas_standalone.tscn stop: @lsof -ti :9876 | xargs -r kill 2>/dev/null || true @echo "Stopped any running server on port 9876" # --- Test --- test: test-server test-client test-server: tests/run-rust fixtures: cd server && cargo test --test gen_fixtures -- --ignored fixtures-gauntlet: cd server && cargo test --test gen_gauntlet_fixtures -- --ignored fixtures-client: @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } @echo "Generating GDScript fixtures for Rust decoder..." $(GODOT) --headless --path client -s res://tests/gen_client_fixtures.gd @echo "Verifying Rust can decode GDScript fixtures..." cd server && cargo test --test serialization gdscript_generated_fixtures @echo "--- Fixtures-client: PASS ---" golden-diff: @echo "Regenerating golden output for comparison..." @mkdir -p .cache/golden @cp server/tests/golden/proof_room_tick_10.json .cache/golden/before.json @cd server && UPDATE_GOLDEN=1 cargo test --test golden_suite -- proof_room_tick_10_matches_golden; \ rc=$$?; \ cp ../server/tests/golden/proof_room_tick_10.json ../.cache/golden/after.json 2>/dev/null; \ cp ../.cache/golden/before.json ../server/tests/golden/proof_room_tick_10.json; \ if [ $$rc -ne 0 ]; then echo "Cargo test failed (golden file restored)."; exit $$rc; fi @if diff -q .cache/golden/before.json .cache/golden/after.json >/dev/null 2>&1; then \ echo "--- Golden file: UP TO DATE ---"; \ else \ echo "--- Golden file has CHANGED ---"; \ echo ""; \ diff --color -u .cache/golden/before.json .cache/golden/after.json || true; \ echo ""; \ echo "Run 'make golden-update' to accept changes."; \ exit 1; \ fi golden-update: @echo "Regenerating golden files..." @cd server && UPDATE_GOLDEN=1 cargo test --test golden_suite -- proof_room_tick_10_matches_golden @cd server && UPDATE_GOLDEN=1 cargo test --test cascade_golden @git add server/tests/golden/ @echo "--- Golden file updated and staged ---" @echo "Review with: git diff --cached -- server/tests/golden/" test-client: tests/run-godot test-ipc-fixtures: tests/run-ipc-fixtures test-ipc-protocol: tests/run-ipc-protocol test-ipc-integration: tests/run-ipc-integration test-ipc-benchmark: tests/run-ipc-benchmark # Python with tooling deps (numpy/scipy) — .venv from `make setup-venv`, else system python3 VENV_PY := $(shell test -x .venv/bin/python && echo .venv/bin/python || echo python3) # Tooling test gate (T-1066) — called by pre-push when tooling/ changed. # 1. planet-gen determinism guard (#963): simulating the same body twice must # be bit-identical (guards the expensive 271-body heightmap bake). # Exit 2 = no testable body found — warn, don't block. # 2. import_economics --dry-run against the committed DB: full parse + # structural/coverage validation, no writes. Exit 2 = coverage-gate # warning (D-175) — tolerated, matching regen-db's treatment. test-tooling: @echo " [test-tooling] planet-gen determinism guard (#963)..." @rc=0; $(VENV_PY) tooling/planet-gen/test_sim_determinism.py || rc=$$?; \ if [ $$rc -eq 2 ]; then \ echo " WARNING: determinism guard found no testable body (exit 2) — not blocking"; \ elif [ $$rc -ne 0 ]; then \ echo " FAIL: planet_simulation determinism drift (exit $$rc)"; exit $$rc; \ fi @echo " [test-tooling] oasis ring-scaling pin (T-964, PR #210 review)..." @$(VENV_PY) tooling/planet-gen/test_oasis_ring_scaling.py 2> .cache/test-tooling-oasis-ring.log || \ { echo " FAIL: oasis ring scaling — log follows:"; cat .cache/test-tooling-oasis-ring.log; exit 1; } @echo " [test-tooling] economy_import.traits validation units (T-995/PR #173 H2)..." @mkdir -p .cache @python3 tooling/economy-db/test_traits.py 2> .cache/test-tooling-traits.log || \ { echo " FAIL: traits validation units — log follows:"; cat .cache/test-tooling-traits.log; exit 1; } @echo " [test-tooling] atlas_city_names/atlas_feature_names idempotency (T-964)..." @python3 tooling/economy-db/test_atlas_idempotency.py 2> .cache/test-tooling-atlas-idempotency.log || \ { echo " FAIL: atlas name-pool idempotency — log follows:"; cat .cache/test-tooling-atlas-idempotency.log; exit 1; } @echo " [test-tooling] import_economics --dry-run (committed DB)..." @rc=0; python3 tooling/economy-db/import_economics.py --dry-run \ > .cache/test-tooling-dryrun.log 2>&1 || rc=$$?; \ if [ $$rc -eq 2 ]; then \ echo " WARNING: coverage gate warning (exit 2) — not blocking (matches regen-db)"; \ elif [ $$rc -ne 0 ]; then \ echo " FAIL: import_economics --dry-run exited $$rc — log follows:"; \ cat .cache/test-tooling-dryrun.log; exit $$rc; \ fi @echo " test-tooling: PASS" # --- Clean --- clean-imports: @echo "Removing Godot import cache..." rm -rf client/.godot/imported/ @echo "Re-importing assets (this may take a while on first run)..." $(GODOT) --headless --path client --import --quit @echo "Import cache rebuilt." # --- Lint --- lint: lint-server lint-client lint-python lint-python: ruff check tooling/ lint-server: cd server && cargo clippy -- -D warnings cd server && cargo fmt --check lint-client: @test -n "$(GODOT)" || { echo "Godot not found. Run 'make setup' first."; exit 1; } @echo "Checking GDScript for errors..." @$(GODOT) --headless --path client --quit 2>&1 | grep -i "SCRIPT ERROR" && { echo "GDScript errors found"; exit 1; } || echo "No script errors found" # --- Pre-PR verification --- pre-pr: pre-pr-lint pre-pr-build pre-pr-test pre-pr-validate pre-pr-fixtures audit deny @echo "" @echo "=== PRE-PR: ALL CHECKS PASSED ===" @echo "Safe to create PR." pre-pr-lint: lint-server lint-client @echo "--- Lint: PASS ---" pre-pr-build: build-server build-client @echo "--- Build: PASS ---" pre-pr-test: test-server test-client @echo "--- Tests: PASS ---" pre-pr-validate: validate-content check-fact-ids check-star-map @echo "--- Content validation: PASS ---" pre-pr-fixtures: @echo "Checking Rust->GDScript fixture staleness..." @cd server && cargo test --test gen_fixtures -- --ignored @if git diff --quiet client/tests/fixtures/; then \ echo "--- Rust fixtures: UP TO DATE ---"; \ else \ echo ""; \ echo "--- RUST FIXTURES STALE ---"; \ echo " Protocol changed but fixtures not regenerated."; \ echo " Stale fixtures make all client tests FALSE POSITIVES."; \ echo ""; \ echo " Changed files:"; \ git diff --stat client/tests/fixtures/; \ echo ""; \ echo " Fix: commit the updated fixtures with your protocol change."; \ exit 1; \ fi @echo "Checking GDScript->Rust fixture staleness..." @$(MAKE) fixtures-client @if git diff --quiet server/tests/fixtures/gdscript/; then \ echo "--- GDScript fixtures: UP TO DATE ---"; \ else \ echo ""; \ echo "--- GDSCRIPT FIXTURES STALE ---"; \ echo " Protocol changed but GDScript fixtures not regenerated."; \ echo " Stale fixtures make cross-encoder tests FALSE POSITIVES."; \ echo ""; \ echo " Changed files:"; \ git diff --stat server/tests/fixtures/gdscript/; \ echo ""; \ echo " Fix: commit the updated fixtures with your protocol change."; \ exit 1; \ fi # Branch-specific variants (faster, scope-appropriate) pre-pr-server: lint-server build-server test-server pre-pr-fixtures audit deny @echo "=== Server pre-PR: PASSED ===" pre-pr-client: lint-client build-client test-client check-star-map @echo "=== Client pre-PR: PASSED ===" pre-pr-content: validate-content check-fact-ids checklist-validate atlas-verify @echo "=== Content pre-PR: PASSED ===" # --- CI (run locally) --- ci: ci-server ci-client ci-server: lint-server build-server test-server ci-client: lint-client build-client test-client # --- Database --- economy-db: ## Import economics data (commodities, chains, gate links) into systems.db @echo " Generating minor brands (D-189 #829)..." @tooling/generate-brands @python3 tooling/economy-db/import_economics.py regen-db: ## Regenerate systems.db from all sources and stamp meta table (#855, #856) @# Run as a single shell so `set -e` covers all steps. Without this @# each recipe line was a fresh shell and a failure in step 1 did not @# halt step 2, which could produce stale data with a fresh stamp @# (PR #136 review T4). import_economics' exit code 2 is a valid @# coverage-gate-warning state (DB and stamp committed), not an error, @# so it's explicitly tolerated. Any other non-zero exit halts the @# pipeline immediately. The atlas city/road/river geometry generator @# was retired in #951 (D-223); import_economics now owns the atlas @# index — it loads the names-only pool into atlas_city_names and empties @# the geometry tables (the server cascade fills them, Phase 4). @set -e; \ echo " [regen-db] Importing economics data (runs generate_brands internally)..."; \ ec=0; python3 tooling/economy-db/import_economics.py || ec=$$?; \ if [ $$ec -ne 0 ] && [ $$ec -ne 2 ]; then exit $$ec; fi; \ echo ""; \ echo " regen-db complete — systems.db is up to date and stamped."; \ echo " Stage it with: git add server/data/systems.db" check-systems-db: ## Verify systems.db meta stamp matches current generator sources (#857) @python3 tooling/check-systems-db-stamp --verbose econ-sim: ## Build the economics simulation binary (Layer 1+2: Leontief + tâtonnement trade) @cargo build --manifest-path tooling/econ-sim/Cargo.toml --release @echo "Built: tooling/econ-sim/target/release/econ-sim" econ-sim-run: ## Run a quick economics simulation (100 ticks, output to /tmp/econ-sim.csv) @tooling/econ-sim/target/release/econ-sim --ticks 100 --output /tmp/econ-sim.csv @echo "Output: /tmp/econ-sim.csv" econ-sim-stability: ## Run D-179 stability checks (Tests 1 and 2) @tooling/econ-sim/target/release/econ-sim --stability-check # --- Decisions (pql) --- # Decision records are markdown-sourced under governance/{decisions,questions,rejected}/ # and indexed into .pql/pql.db by `pql decisions sync`. Per-decision ticket coverage: # `pql decisions show --with-tickets`. Cross-references: `pql decisions refs `. decisions-sync: @pql decisions sync decisions-active: @pql decisions list --type confirmed decisions-validate: @pql decisions validate # --- Content Validation --- validate-content: @tooling/validate-content check-fact-ids: @tooling/check-fact-ids atlas-verify: @tooling/atlas-verify docs/atlas/proposals/*.json audit: cd server && cargo audit deny: cd server && cargo deny check checklist-validate: @tooling/validate-checklist --check checklist-generate: @tooling/validate-checklist check-star-map: @python3 tooling/generate-star-map-data.py --check # Regenerate client/data/star_map_data.json from systems.db + wiki. Depends on # nothing — call this after any systems.db change (e.g. the server atlas # pipeline populating terrain_reference in #839) so the atlas viewer picks up # the new fields. pre-pr-client / pre-pr-validate assert staleness via # check-star-map and will fail if this step is skipped. star-map-data: @python3 tooling/generate-star-map-data.py perf-baseline: @tooling/perf-baseline # --- Schedule debug (#346) --- debug-schedule: @echo "Dumping bevy_ecs schedule graph..." @cd server && cargo run --bin settled-reach-server -- --dump-schedule # --- Visual test harness --- screenshot: @tests/run-visual --screenshot $(SCENARIO) visual-movie: @tests/run-visual --movie $(FLOW) test-visual: @tests/run-visual visual-update: @tests/run-visual --update LIBCLANG_PATH ?= /usr/lib64/rocm/llvm/lib BINDGEN_CLANG_ARGS ?= -I/usr/lib64/rocm/llvm/lib/clang/19/include SR_VOICE_ENV = LIBCLANG_PATH=$(LIBCLANG_PATH) BINDGEN_EXTRA_CLANG_ARGS="$(BINDGEN_CLANG_ARGS)" SR_VOICE_PORT ?= 8321 build-sr-voice: cd server/sr-voice && $(SR_VOICE_ENV) cargo build --release serve-sr-voice: cd server/sr-voice && $(SR_VOICE_ENV) cargo run --release -- serve $(ARGS) run-sr-voice: cd server/sr-voice && $(SR_VOICE_ENV) cargo run --release -- $(ARGS) stop-sr-voice: @lsof -ti :$(SR_VOICE_PORT) | xargs -r kill 2>/dev/null || true @echo "Stopped sr-voice on port $(SR_VOICE_PORT)" test-voice-mock: @echo "Running voice pipeline test (mock sr-voice)..." cd server && SR_VOICE_MOCK=1 cargo test --test voice_pipeline -- --nocapture @echo "Results: .tmp/voice-test/results.txt" test-voice-real: @echo "Running voice pipeline test (real sr-voice + Gemma 2B)..." @test -f server/sr-voice/target/release/sr-voice || { echo "Build sr-voice first: make build-sr-voice"; exit 1; } @test -f server/models/gemma2.gguf || { echo "Model not found: server/models/gemma2.gguf"; exit 1; } cd server && cargo test --test voice_pipeline -- --nocapture @echo "Results: .tmp/voice-test/results.txt" # --- Asset manifest --- manifest: @tooling/generate-character-manifest @echo "Manifest regenerated — commit client/assets/characters/manifest.json if changed." # --- Clean --- clean: cd server && cargo clean || true rm -rf .cache/* rm -rf client/reports @echo "Clean complete."