From 655d25e2c1b9c1cb38ea8819819221d0ba79abdc Mon Sep 17 00:00:00 2001 From: Natalie Date: Sat, 27 Jun 2026 09:47:43 -0400 Subject: [PATCH] =?UTF-8?q?docs(@projects/@magic-civilization):=20?= =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Rail-2=20=E2=80=94=20document=20the=20t?= =?UTF-8?q?wo-path=20content=20divergence=20+=20track=20an=20enforcement?= =?UTF-8?q?=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rust-source-of-truth.md: add the "two-path divergence" rule to the canonical content store section. Content reaches the sim two ways — in-game (GDScript DataLoader reads JSON at runtime, projection.rs:41) and headless (Rust falls back to a compile-time include_str!/hardcode copy, dispatch.rs:410). A balance constant hardcoded in a crate is both a Rail-2 violation and a silent second copy that drifts from the JSON — and headless is where the AI trains. Rule: grep public/resources + public/games/**/data for a JSON home before adding a numeric balance const; if it exists, LOAD it (OnceLock+include_str!, never std::fs in shared sim code). References the p3-28 ContentRegistry endgame. p3-28: add the matching "Rail-2 verify gate (enforcement)" acceptance bullet — tools/check-no-rust-hardcoded-content.py + a verify step to catch the next hardcode, best landed alongside the ContentRegistry. Co-Authored-By: Claude Opus 4.8 --- .project/objectives/p3-28-modular-turn-architecture.md | 7 +++++++ .../claude/dot-claude/instructions/rust-source-of-truth.md | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) 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