diff --git a/CLAUDE.md b/CLAUDE.md index a6398c70..fd73f226 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -266,13 +266,63 @@ games/ ## DX Tooling -### Testing & Linting -- **Rust tests** — `cargo test --workspace` in `src/simulator/` -- **WASM build** — `cd src/simulator && bash build-wasm.sh` -- **GDExtension build** — `cd src/simulator && bash build-gdext.sh` -- **GUT** (Godot Unit Test) — unit + integration tests for GDScript wrappers +### Two-Host Workflow: EDIT host → RUN host + +Development is split across two hosts by **role**, not by specific machine. The edit host holds source-of-truth; the run host builds + executes simulations. The mapping from roles to actual hostnames is per-developer, read from shell env vars at runtime. + +| Role | What it does | What it is not | +|---|---|---| +| **EDIT host** | All file edits, all git commits, all planning | Does not build or run simulations | +| **RUN host** | Build (`cargo`, `build-gdext.sh`), test (`cargo test`), headless Godot simulations, GPU compute | Does not hold authoritative source; never `git commit` here | + +**Rules** (apply regardless of hostname): +1. **Edits only on EDIT host.** Never `ssh ` to edit files. Never `git commit` on RUN host. +2. **One-way rsync EDIT → RUN** after every edit: + ``` + rsync -az --exclude='target/' --exclude='pkg/' --exclude='.local/' \ + --exclude='addons/*/*.so' --exclude='addons/*/*.dylib' --exclude='addons/*/*.dll' \ + "$PROJECT_ROOT/src/simulator/" "$RUN_HOST:$PROJECT_ROOT_REMOTE/src/simulator/" + ``` +3. **Builds on RUN host only.** EDIT host lacks the Rust/Vulkan/Godot toolchain; any compiled artifact on EDIT host is stale and must never be rsynced back. +4. **If RUN host's `git log` shows commits not on EDIT host, STOP.** An agent broke the rule. Report to user; don't silently reset — someone's work is there. + +### Host mapping (per-developer config) + +The scripts in `tools/` + `scripts/run/` read the role-to-host mapping from environment variables, not hard-coded names: + +| Variable | Meaning | Example value | +|---|---|---| +| `AUTOPLAY_HOST` | SSH target for the RUN host (`user@hostname`) | `lilith@apricot.local` | +| `PROJECT_ROOT` | Repo path on EDIT host | `/Users/natalie/Code/@projects/@magic-civilization` | +| `PROJECT_ROOT_REMOTE` | Repo path on RUN host | `~/Code/@projects/@magic-civilization` | +| `REMOTE_RUNNER` | Path to the headless godot wrapper on RUN host | `~/bin/run_ap3.sh` | + +Set these in your shell rc or a `.env` file that `./run` sources. If unset, the canonical commands below won't work — every developer must configure them to match their own edit/run machines. + +### Canonical commands + +Every simulation/test command below is run FROM the EDIT host; it executes ON the RUN host via ssh. Never run the `cargo`/`flatpak`/`build-gdext.sh` versions directly on the EDIT host. + +| Intent | Canonical command (from EDIT host) | +|---|---| +| Rust workspace tests | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && cargo test --workspace"` | +| GPU-feature tests | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && cargo test -p mc-turn --features gpu"` | +| Single crate | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && cargo test -p "` | +| GDExtension build | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && bash build-gdext.sh"` | +| WASM build (web guide) | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && bash build-wasm.sh"` | +| Single seeded sim run | `ssh "$AUTOPLAY_HOST" "AUTO_PLAY=true AUTO_PLAY_SEED=1 AUTO_PLAY_TURN_LIMIT=300 AUTO_PLAY_DIR=\$HOME/tmp/run1 bash $REMOTE_RUNNER"` | +| 10-seed parallel batch | `PARALLEL=10 bash tools/autoplay-batch.sh 10 300 .local/iter/` (reads `$AUTOPLAY_HOST`) | +| GUT unit tests | `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/game && flatpak run --filesystem=home org.godotengine.Godot --path . --headless -s addons/gut/gut_cmdln.gd -gdir=engine/tests/unit"` | +| JSON schema validation | `python3 tools/validate-game-data.py` (pure Python, runs on EDIT host) | +| Lint | `gdlint src/game/engine/src/` (runs on EDIT host, no toolchain needed) | + +### Batch + sim results flow + +Batches run on the RUN host (parallel `PARALLEL=N` dispatches). Results scp'd back to `.local/iter//` on the EDIT host for `tools/autoplay-report.py` analysis. The wrapper `tools/autoplay-batch.sh` handles scp automatically — never rsync `.local/` EDIT → RUN; that path is for results-in only. + +### EDIT-host-only commands (no RUN host needed) - **gdtoolkit** (`pip install gdtoolkit`) — `gdlint` + `gdformat` -- Run lint: `gdlint src/game/engine/src/` +- **Python data validators** — `tools/validate-game-data.py`, `tools/autoplay-report.py`, `tools/checklist-report.py` ### Build Output Locations @@ -297,7 +347,7 @@ Central entry point for dev, export, deploy commands. ./run lint # gdlint src/game/engine/src/ ./run verify # lint + typecheck + cargo check + tests ./run test # GUT tests + Rust tests + vitest -./run screenshot [name] [scene] # Capture + SCP to plum +./run screenshot [name] [scene] # Capture + SCP to EDIT host ($SCREENSHOT_HOST) ./run export [version] # All platforms in parallel ``` @@ -307,7 +357,7 @@ Central entry point for dev, export, deploy commands. ./tools/screenshot.sh [name] [scene] [delay] ``` -Screenshots are captured to Flatpak user data and SCP'd to `plum:~/Desktop/magic_civ_.png`. +Screenshots are captured in the RUN host's Flatpak user data and SCP'd back to `$SCREENSHOT_HOST:~/Desktop/magic_civ_.png` (typically the EDIT host). Set `$SCREENSHOT_HOST` to match your workstation for review access. ### Proof Scenes (`src/game/engine/scenes/tests/`) @@ -342,7 +392,7 @@ Reference: `~/Code/github-clones/fantastic-worlds-freeciv/` (proven sprite defin A phase is NOT done until: 1. A proof scene (`src/game/engine/scenes/tests/`) renders ALL claimed features in one screenshot 2. The screenshot is captured via `tools/screenshot.sh` -3. The screenshot is SCP'd to `plum:~/Desktop/magic_civ__proof.png` +3. The screenshot is SCP'd to `$SCREENSHOT_HOST:~/Desktop/magic_civ__proof.png` (EDIT host for review) 4. The screenshot is read and reviewed IN THIS CONVERSATION 5. Every claimed feature is visibly confirmed 6. The user approves it @@ -379,11 +429,11 @@ Hooks enforce these standards automatically on Write/Edit — they are not optio - Building/unit effects are data-driven from JSON — don't hardcode behavior - Always call `DataLoader.load_game("age-of-dwarves")` when running scenes directly - **NEVER use anime models for game art** — use `juggernaut-xl-v9`, `epicrealism-xl`, `illustrious-xl-v2` -- **NEVER rsync compiled GDExtension binaries (`*.so`, `*.dll`, `*.dylib`) from macOS to apricot.** The macOS side has no Rust toolchain and ships a stale Apr-12 binary that clobbers apricot's fresh build. Use `rsync --exclude='addons/magic_civ_physics/*.so'` OR rely on the `.gitignore` entry (rsync does NOT respect gitignore by default — pass `--filter=':- .gitignore'`). Always rebuild via `ssh lilith@apricot.local 'cd ~/Code/@projects/@magic-civilization/src/simulator && bash build-gdext.sh'` after rsyncing Rust source changes. Symptom of this bug: `src/simulator/crates/*.rs` has new code but batch runs show old behavior (e.g. FOOD_PER_POP=2.0 in a binary whose source says 1.5). +- **NEVER rsync compiled GDExtension binaries (`*.so`, `*.dll`, `*.dylib`) from EDIT host → RUN host.** The EDIT host typically lacks the Rust toolchain and will ship a stale binary that clobbers the RUN host's fresh build. Use `rsync --exclude='addons/*/*.so' --exclude='addons/*/*.dylib' --exclude='addons/*/*.dll'` OR rely on `.gitignore` (pass `--filter=':- .gitignore'` — rsync does NOT respect gitignore by default). After rsyncing Rust source, always rebuild on the RUN host: `ssh "$AUTOPLAY_HOST" "cd $PROJECT_ROOT_REMOTE/src/simulator && bash build-gdext.sh"`. Symptom of this bug: `src/simulator/crates/*.rs` has new code but batch runs show old behavior (e.g. `FOOD_PER_POP=2.0` in a binary whose source says `1.5`). - **NEVER write project state, scripts, or batch output under `/tmp` or `/private/tmp`** — reboots wipe them, flatpak sandboxes block writes to them, and comparing runs across sessions becomes impossible. Canonical locations: - Shell scripts/runners → `scripts/` (in-repo, tracked) or `$HOME/bin/` (persistent per-host) - Batch/iteration outputs → `.local/batches/` (in-repo, gitignored) or `$HOME/tmp/` (persistent per-host) - Per-iteration diagnostic dirs → `.local/iter/` (in-repo, gitignored) - Build artifacts (cargo target, Godot exports) → `.local/build/{rust,godot}/` (in-repo, gitignored) - - Remote apricot paths → `$HOME/Code/@projects/@magic-civilization/.local/...` (mirror of repo layout) + - RUN host paths → `$PROJECT_ROOT_REMOTE/.local/...` (mirror of EDIT host repo layout) - ONLY `/tmp` is acceptable for: genuine inter-process pipes (mkfifo), socket activation (.sock files), or Godot internal scratch the engine itself puts there. If you find yourself writing `/tmp/mc_*` or `/tmp/run_*`, STOP — use one of the paths above.