diff --git a/.project/objectives/p3-28-modular-turn-architecture.md b/.project/objectives/p3-28-modular-turn-architecture.md index b2cf0c3d..b1be9363 100644 --- a/.project/objectives/p3-28-modular-turn-architecture.md +++ b/.project/objectives/p3-28-modular-turn-architecture.md @@ -32,6 +32,13 @@ revealed three SOLID/DRY/DIP debts. "Foundation first" tackled the layering + ph `res://` bytes across FFI, the web guide fetches packs → bytes across bindgen — with `include_str!` surviving **only** as the headless/test fallback. So the target is a registry fed by injected bytes, not a path-reading function. +- [ ] **Rail-2 verify gate (enforcement) — catch the next hardcode before it ships.** The gate + has a Rail-1 check (`verify.sh` Step 18 → `tools/check-no-gdscript-sim-logic.py`) but **no Rail-2 + equivalent** for hardcoded game content in Rust. Add `tools/check-no-rust-hardcoded-content.py` + + a verify step that flags balance-looking `const`/`static` tables in `mc-*` crates which duplicate + a JSON file. ⚠ heuristic — needs a low-false-positive design (likely a small allowlist/registry of + `(json_file, owning_module)` pairs rather than a blind numeric-array grep). Best landed alongside + the `ContentRegistry` so the check becomes "does the registry own this?", not a regex. - [ ] **Dedup the ad-hoc `include_str!` content sites (Opportunity A, same arc)** — verified 2026-06-27: **8 JSON-config `include_str!` sites** across simulator crates, each rolling its own `OnceLock` + a fragile relative path (6 at `../../../../../`). `treaty_rules.json` is diff --git a/tooling/claude/dot-claude/instructions/rust-source-of-truth.md b/tooling/claude/dot-claude/instructions/rust-source-of-truth.md index fa63fa28..cca645d6 100644 --- a/tooling/claude/dot-claude/instructions/rust-source-of-truth.md +++ b/tooling/claude/dot-claude/instructions/rust-source-of-truth.md @@ -14,7 +14,9 @@ There is **no permanent GDScript carve-out** for AI (Rail-1). New AI work lands ## Canonical content store -**JSON game packs remain the canonical content store.** Stats, costs, effects, thresholds — all in `public/games/age-of-dwarves/data/*.json`. Neither Rust nor GDScript hardcodes game content. +**JSON game packs remain the canonical content store.** Stats, costs, effects, thresholds — all in `public/games/age-of-dwarves/data/*.json` (+ `public/resources/*`). Neither Rust nor GDScript hardcodes game content. + +> **The two-path divergence (why this rule gets violated silently).** Content reaches the sim two ways: **in-game**, GDScript `DataLoader` reads the JSON at runtime (`mc-player-api/src/projection.rs:41`); **headless** (tests, CI, AI self-play, WASM guide), Rust falls back to a compile-time `include_str!`/hardcode copy (`dispatch.rs:410`). A balance constant hardcoded in a Rust crate is both a Rail-2 violation **and** a second copy that drifts from the JSON with no error — and the headless path is where the AI trains. **Before adding a numeric balance constant in a crate, grep `public/resources/**` + `public/games/**/data/**` for an existing JSON home; if it exists, LOAD it (`OnceLock`+`include_str!` — WASM/gdext-safe, never `std::fs` in shared sim code), don't hardcode.** Instance fixed 2026-06-27: `mc-combat` promotion XP thresholds (`promotions.rs` const vs `promotions.json`). The structural endgame (one host-fed `ContentRegistry` both paths read) is tracked on `p3-28`. - UI labels resolve through `ThemeVocabulary.lookup(engine_key)` — never hardcode theme strings - Sprites resolve through `ThemeAssets.resolve(path)` — never hardcode asset paths