From d31d8af1807efa58e154863c92f35c58d91089e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Fri, 1 May 2026 11:44:17 +0200 Subject: [PATCH] docs: nested CLAUDE.md via init-deep Slim root CLAUDE.md (90 lines) and add 7 nested CLAUDE.md files for progressive disclosure. Each nested file loads automatically when Claude reads files in that directory. Nested files: - game/ scenarios + NPC phrases (source-of-truth) - tools/ Python tooling (Runtime 3, TTS, MCP, dev CLI) - tests/ Python unittest patterns - specs/ contracts governance - frontend-scratch-v2/ React 19 + Blockly + Vite (legacy) - frontend-v3/ pnpm monorepo (production target) - desktop/ Electron Zacus Studio (macOS) Root CLAUDE.md gains a Where-to-Look table and pointer to nested guidance. ESP32_ZACUS submodule is intentionally not covered (separate repo with its own CI). --- CLAUDE.md | 101 +++++++++++++++++----------------- desktop/CLAUDE.md | 45 +++++++++++++++ frontend-scratch-v2/CLAUDE.md | 47 ++++++++++++++++ frontend-v3/CLAUDE.md | 48 ++++++++++++++++ game/CLAUDE.md | 34 ++++++++++++ specs/CLAUDE.md | 31 +++++++++++ tests/CLAUDE.md | 34 ++++++++++++ tools/CLAUDE.md | 40 ++++++++++++++ 8 files changed, 328 insertions(+), 52 deletions(-) create mode 100644 desktop/CLAUDE.md create mode 100644 frontend-scratch-v2/CLAUDE.md create mode 100644 frontend-v3/CLAUDE.md create mode 100644 game/CLAUDE.md create mode 100644 specs/CLAUDE.md create mode 100644 tests/CLAUDE.md create mode 100644 tools/CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md index 33d2869..b1a83fb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,93 +1,90 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guidance for Claude Code working in this repository. ## Overview -**Le Mystère du Professeur Zacus** — hybrid educational escape room game with ESP32-S3 hardware, React authoring studio, and a portable Runtime 3 scenario engine. Scenarios are authored in YAML, compiled to a portable IR, and executed on both web and ESP32 targets. +**Le Mystère du Professeur Zacus** — hybrid educational escape room game with ESP32-S3 hardware, React authoring studios, and a portable Runtime 3 scenario engine. Scenarios are authored in YAML, compiled to a portable IR, and executed on both web and ESP32 targets. ## Build & Test ```bash -# Full validation pipeline -make all-validate +make all-validate # Full pipeline (scenario + audio + printables + runtime3) +make content-checks # Schema validation only -# Content checks (schema validation for scenarios, audio, printables) -make content-checks - -# Runtime 3 — compile, simulate, verify, test -make runtime3-compile # YAML → Runtime 3 IR -make runtime3-simulate # simulate scenario execution -make runtime3-verify # verify pivot logic -make runtime3-test # Python unittest suite - -# Frontend (React 19 + Blockly + Vite) -cd frontend-scratch-v2 -npm test # Vitest (18 tests) -npm run build # tsc -b + vite build -npx vitest run tests/specific.test.ts # single test +# Runtime 3 +make runtime3-compile # YAML → IR +make runtime3-simulate # Execute IR +make runtime3-verify # Verify pivots +make runtime3-test # Python unittest # Compile a specific scenario python3 tools/scenario/compile_runtime3.py game/scenarios/zacus_v2.yaml # Dev CLI (12 actions) ./tools/dev/zacus.sh content-checks -./tools/dev/zacus.sh runtime3-compile ./tools/dev/zacus.sh voice-bridge start|stop|status|test ``` +Frontend, desktop, and tooling commands live in their nested CLAUDE.md. + ## Architecture -### Data Flow ``` YAML scenario → compile_runtime3.py → Runtime 3 IR → ESP32 / Web player ↓ Voice bridge → Piper TTS (Tower:8001) ↓ - Hints engine → /hints/ask (3 puzzles, 3 levels) + Hints engine → /hints/ask ``` -### Key Components +Key surfaces: +- **Scenario IR**: `game/scenarios/zacus_v2.yaml` → `tools/scenario/compile_runtime3.py` → portable Runtime 3 IR. Contract: `specs/ZACUS_RUNTIME_3_SPEC.md`. +- **Authoring**: `frontend-scratch-v2/` (legacy single-app) and `frontend-v3/` (pnpm monorepo, dashboard + editor + simulation). +- **Firmware**: `ESP32_ZACUS/` submodule (separate repo, separate CI). Freenove ESP32-S3 + PlatformIO. NPC engine, voice pipeline, vision/QR, media manager. +- **Voice / NPC**: Piper TTS on Tower:8001 (zacus voice = tom-medium). NPC phrases in `game/scenarios/npc_phrases.yaml`. MP3 pool generator: `tools/tts/generate_npc_pool.py`. +- **MCP hardware**: `tools/dev/mcp_hardware_server.py` (stdio, 6 tools). +- **Desktop hub**: `desktop/` (Electron, macOS, bundles V3 frontends, talks USB serial). -- **Scenario engine**: `game/scenarios/zacus_v2.yaml` is the canonical source. Runtime 3 spec in `specs/ZACUS_RUNTIME_3_SPEC.md`. -- **Frontend studio** (`frontend-scratch-v2/`): React 19 + Blockly 12.4 + Monaco Editor + Zod validation. Vite bundler, TypeScript. -- **ESP32 firmware** (`ESP32_ZACUS/` submodule): Freenove ESP32-S3, PlatformIO build. Separate repo with its own CI. Contains voice pipeline scaffold, audio/vision/QR detection, media manager. -- **Runtime 3 compiler** (`tools/scenario/compile_runtime3.py`): YAML → portable IR with pivots, zones, triggers. -- **Voice pipeline**: Piper TTS on Tower:8001 (3 voices: zacus=tom-medium, siwis, upmc), ESP-SR for wake word. Voice bridge routes `[HINT:puzzle:level]` to hints engine. -- **MCP hardware server** (`tools/dev/mcp_hardware_server.py`): 6 tools, stdio transport for hardware interaction. -- **TUI dashboard** (`tools/dev/zacus_tui.py`): 12 actions, logs, CI mode. -- **Analytics**: ESP32 module + 6 web endpoints + Dashboard UI. -- **NPC Engine** (`ESP32_ZACUS/ui_freenove_allinone/include/npc/npc_engine.h` + `src/npc/npc_engine.cpp`): Lightweight C state machine for Professor Zacus NPC. Trigger rules (stuck timer, QR scan, fast/slow progress, hint request), mood system (neutral/impressed/worried/amused), hybrid audio routing (live Piper TTS when Tower reachable, SD card MP3 fallback). NPC phrase bank in `game/scenarios/npc_phrases.yaml`. -- **TTS Client** (`ESP32_ZACUS/ui_freenove_allinone/include/npc/tts_client.h` + `src/npc/tts_client.cpp`): HTTP client for Piper TTS on Tower:8001 with health-check, PSRAM WAV buffer, and SD card fallback. Voice: tom-medium. -- **NPC Phrase Bank** (`game/scenarios/npc_phrases.yaml`): All Professor Zacus lines in French, organized by category: hints (3 levels × 6 scenes), congratulations, warnings, personality comments (by mood), adaptation phrases (skip/challenge/timer), narrative bridges, false leads, and ambiance (intro/outro/idle). -- **NPC MP3 Pool Generator** (`tools/tts/generate_npc_pool.py`): Python tool that reads `npc_phrases.yaml`, calls Piper TTS API for each phrase, writes MP3 files to `hotline_tts/`, and generates `hotline_tts/manifest.json`. Idempotent (skips already-generated files). Run: `python3 tools/tts/generate_npc_pool.py [--dry-run]`. +## Where to Look -### AI Integration -- 6 AI agent definitions in `.github/agents/` (voice, tts, vision, hints, audio_gen, mcp) -- Hints engine: anti-cheat, 3 difficulty levels, per-puzzle context -- Spec: `specs/AI_INTEGRATION_SPEC.md` +| Task | Location | +|------|----------| +| Edit scenarios, NPC phrases, prompts | `game/` | +| Modify Runtime 3 compiler / validators / TTS pool / dev CLI | `tools/` | +| Authoring UI (legacy) | `frontend-scratch-v2/` | +| Authoring UI / dashboard / simulation (current) | `frontend-v3/` | +| Zacus Studio macOS app | `desktop/` | +| Add or change a contract spec | `specs/` | +| Python tests (Runtime 3, NPC) | `tests/` | +| Firmware code | `ESP32_ZACUS/` submodule (own repo) | ## Canonical Files | File | Role | |------|------| -| `game/scenarios/zacus_v2.yaml` | Scenario source of truth (v3, Runtime 3) | -| `game/scenarios/npc_phrases.yaml` | Professor Zacus NPC phrase bank (all categories, French) | -| `tools/tts/generate_npc_pool.py` | NPC MP3 pool generator (Piper TTS → hotline_tts/) | -| `specs/ZACUS_RUNTIME_3_SPEC.md` | Runtime contract definition | +| `game/scenarios/zacus_v2.yaml` | Scenario source of truth | +| `game/scenarios/npc_phrases.yaml` | NPC phrase bank (FR) | +| `specs/ZACUS_RUNTIME_3_SPEC.md` | Runtime contract | | `specs/AI_INTEGRATION_SPEC.md` | AI layer architecture | -| `Makefile` | Main automation entry point | +| `Makefile` | Top-level automation | | `docs/QUICKSTART.md` | Getting started | | `docs/DEPLOYMENT_RUNBOOK.md` | Field deployment | -| `docs/debt/codex-firmware-fixes-to-apply.md` | Pending firmware fixes (I2S, CORS, AP password) | - -## Language & Communication - -- User speaks **French**, code and docs in **English** -- Respond in French for conversation, English for code/comments/commits +| `docs/debt/codex-firmware-fixes-to-apply.md` | Pending firmware fixes | ## Infrastructure -- **Tower** (`clems@192.168.0.120`): Piper TTS FR on port 8001 -- **KXKM-AI** (`kxkm@kxkm-ai`): RTX 4090, GPU inference -- SSH is key-based only, never use sshpass +- **Tower** (`clems@192.168.0.120`): Piper TTS FR on `:8001` (voices: tom-medium / siwis / upmc). +- **KXKM-AI** (`kxkm@kxkm-ai` via Tailscale): RTX 4090, GPU inference. +- SSH is key-based only — never `sshpass`. + +## Language + +User speaks French → respond in French. Code, comments, commits, docs → English. Full diacritics required in French. + +## Nested Guidance + +Domain-specific rules live in nested `CLAUDE.md` files and load automatically when you read files in those directories. Closest file wins. Current nested: + +- `game/`, `tools/`, `tests/`, `specs/` +- `frontend-scratch-v2/`, `frontend-v3/`, `desktop/` diff --git a/desktop/CLAUDE.md b/desktop/CLAUDE.md new file mode 100644 index 0000000..3f2f9ab --- /dev/null +++ b/desktop/CLAUDE.md @@ -0,0 +1,45 @@ +# Desktop — Zacus Studio + +macOS Electron control hub for Professeur Zacus escape room kits. Bundles the V3 frontends, talks to ESP32 over USB serial, and ships notarised universal/arm64 builds. + +## Layout + +``` +src/ + main/ # Electron main process (Node) — IPC, serial, app lifecycle + preload/ # Context-bridge preload (exposes safe APIs to renderer) + renderer/ # React renderer (UI) +scripts/ + build-frontends.sh # Bundles frontend-v3 apps into resources/ + notarize.js # Apple notarization (post-build hook) +resources/ # Bundled frontend artefacts + assets +``` + +## Commands + +```bash +npm install # Triggers electron-builder install-app-deps + electron-rebuild +npm run dev # Concurrent main + renderer +npm run build # tsc main + vite renderer +npm run build:mac # Universal (x64 + arm64) installer +npm run build:mac-arm64 # Apple Silicon only +npm run rebuild-native # zacus-native addon for current Electron ABI +``` + +## Patterns + +- Three tsconfigs (`tsconfig.main.json`, `tsconfig.renderer.json`, root) — never share between processes; main and renderer have different module systems. +- Serial port access lives in `src/main/` only. Renderer talks via `preload` IPC, never imports `serialport`. +- Native modules (`serialport`, `zacus-native`) need rebuilding for Electron's Node ABI — run `npm run rebuild-native` after Electron upgrades. +- Notarization requires `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` env vars; never commit them. + +## Frontend Bundle + +`scripts/build-frontends.sh` builds `frontend-v3/` apps and copies them into `resources/`. Run it before `electron-builder` if frontend changed. + +## Anti-Patterns + +- Calling `require('serialport')` from renderer (security: nodeIntegration must stay false) +- Hardcoding device paths (`/dev/cu.usbserial-*`) — enumerate at runtime via `serialport.list()` +- Skipping notarization on release builds (Gatekeeper will block) +- Bundling V2 frontend instead of V3 — V2 is dev-only diff --git a/frontend-scratch-v2/CLAUDE.md b/frontend-scratch-v2/CLAUDE.md new file mode 100644 index 0000000..062ab78 --- /dev/null +++ b/frontend-scratch-v2/CLAUDE.md @@ -0,0 +1,47 @@ +# Frontend Scratch V2 + +Authoring studio: React 19 + Blockly 12 + Monaco Editor + Zod + Vite 7 + Vitest 3. TypeScript strict. + +## Layout + +``` +src/ + App.tsx, main.tsx # Entry + components/ # UI (scenario editor, blocks, panels) + lib/ + api.ts # ESP32/runtime HTTP client + runtime3.ts # Runtime 3 IR types + compiler bindings (TS mirror of Python) + scenario.ts # Scenario YAML/JSON helpers + useRuntimeStore.ts # Zustand-style runtime state hook + __tests__/ # Co-located unit tests +tests/ + scenario-runtime3.test.ts # Integration test against real fixtures +``` + +## Commands + +```bash +npm run dev # Vite dev server +npm run build # tsc -b && vite build +npm test # Vitest run (18 tests) +npm run lint # ESLint flat config +``` + +## Patterns + +- `lib/runtime3.ts` is the TS mirror of Python `tools/scenario/runtime3_common.py` — keep field names + optionality identical. When the Python IR changes, update both in the same PR. +- Validate scenario imports through Zod schemas, not raw `JSON.parse` — Zod errors surface bad fixtures during dev. +- Blockly toolbox/blocks live in `components/scenario-editor/` — register custom blocks before `Blockly.inject`. +- Monaco editor language is YAML; load worker via Vite import, never CDN. + +## Tests + +- Vitest config inherits from `vite.config.ts`. Use `vitest run --reporter verbose` for diff output. +- Cross-stack invariant tests (Python compile → TS replay) live in `tests/scenario-runtime3.test.ts`. Run them locally before claiming Runtime 3 work is done. + +## Anti-Patterns + +- Drifting `lib/runtime3.ts` types from the Python compiler — schedule a sync, don't bandaid +- Calling `fetch()` outside `lib/api.ts` — keep network code centralised +- Using `any` to escape Zod typing — narrow with `z.infer` instead +- Importing from `frontend-v3/` packages — V2 and V3 share no runtime code diff --git a/frontend-v3/CLAUDE.md b/frontend-v3/CLAUDE.md new file mode 100644 index 0000000..33c43d7 --- /dev/null +++ b/frontend-v3/CLAUDE.md @@ -0,0 +1,48 @@ +# Frontend V3 (Monorepo) + +pnpm + turbo monorepo. Three apps + three shared packages. Node ≥20, pnpm ≥9. + +## Layout + +``` +apps/ + dashboard/ # Live game-master dashboard (analytics, control) + editor/ # Scenario editor (V3 successor of frontend-scratch-v2) + simulation/ # Runtime simulator / playtest UI +packages/ + scenario-engine/ # Runtime 3 IR + execution (shared core) + shared/ # Cross-app utilities, types, constants + ui/ # Shared component library +``` + +## Commands + +```bash +pnpm install # Bootstrap workspace +pnpm dev # turbo run dev (all apps) +pnpm --filter dashboard dev # Single app +pnpm build # turbo run build +pnpm test # turbo run test +pnpm typecheck # turbo run typecheck +pnpm lint # turbo run lint +``` + +## Patterns + +- `scenario-engine` is the canonical TS Runtime 3 implementation. New apps consume it; do not fork the IR. +- All cross-app types live in `packages/shared` — never copy-paste types between apps. +- Components shared across ≥2 apps move to `packages/ui`. Keep app-local components in `apps//src/components`. +- Turbo cache keys: ensure `inputs` in `turbo.json` cover all source paths; missing globs cause stale cache. + +## V2 vs V3 + +- `frontend-scratch-v2/` is the legacy single-app Vite playground. V3 is the production target. +- When porting features from V2, extract reusable logic into `packages/scenario-engine` or `packages/shared` first. +- Do not symlink or import across V2/V3 boundaries. + +## Anti-Patterns + +- Adding deps to root `package.json` (use the relevant app/package) +- Bypassing turbo with manual `tsc` invocations — breaks cache invalidation +- Circular dependencies between packages (turbo will warn; fix immediately) +- Publishing `packages/*` to npm — they're internal workspace-only diff --git a/game/CLAUDE.md b/game/CLAUDE.md new file mode 100644 index 0000000..103062b --- /dev/null +++ b/game/CLAUDE.md @@ -0,0 +1,34 @@ +# Game Content + +Source-of-truth for scenarios, NPC dialogue, and prompts. Edits here propagate to firmware, frontends, and TTS pools. + +## Canonical Files + +| File | Role | +|------|------| +| `scenarios/zacus_v2.yaml` | Master scenario (Runtime 3 IR source) | +| `scenarios/npc_phrases.yaml` | All Professor Zacus lines (FR), categorised | +| `prompts/` | LLM prompt templates for hints/coherence | + +## Editing Rules + +- IDs are foreign keys: changing `puzzle.id` or `zone.id` breaks audio + printables manifests, firmware bundles, and frontend tests. Sync cross-file when renaming. +- Never edit `exports/` or generated MD — regenerate via `tools/scenario/export_md.py`. +- French dialogue only in `npc_phrases.yaml`; full diacritics required. +- New NPC phrase categories must be added to schema validator and `tools/tts/generate_npc_pool.py`. + +## Validate Before Commit + +```bash +python3 tools/scenario/validate_scenario.py scenarios/zacus_v2.yaml +python3 tools/scenario/compile_runtime3.py scenarios/zacus_v2.yaml +python3 tools/audio/validate_manifest.py ../audio/manifests/zacus_v2_audio.yaml +python3 tools/printables/validate_manifest.py ../printables/manifests/zacus_v2_printables.yaml +``` + +## Anti-Patterns + +- Editing generated outputs in `exports/` instead of YAML source +- Renaming IDs without grepping the repo for cross-references +- Adding NPC phrases without regenerating the MP3 pool +- Mixing French narrative with English code identifiers in YAML values diff --git a/specs/CLAUDE.md b/specs/CLAUDE.md new file mode 100644 index 0000000..b7cefe1 --- /dev/null +++ b/specs/CLAUDE.md @@ -0,0 +1,31 @@ +# Specs + +Contracts between firmware, frontends, tooling, and AI agents. Treat each spec as a versioned interface — a breaking change here triggers work in 2+ subsystems. + +## Canonical Specs + +| Spec | Governs | +|------|---------| +| `ZACUS_RUNTIME_3_SPEC.md` | Scenario IR + execution semantics | +| `AI_INTEGRATION_SPEC.md` | Agent layer (hints, voice, vision, TTS, audio_gen, MCP) | +| `FIRMWARE_WEB_DATA_CONTRACT.md` | ESP32 ↔ frontend HTTP/WebSocket payloads | +| `MCP_HARDWARE_SERVER_SPEC.md` | MCP stdio tools (6) for hardware interaction | +| `MEDIA_MANAGER_*` | Media manager (firmware/frontend/runtime/sync) | +| `STORY_DESIGNER_SCRATCH_LIKE_SPEC.md` | Frontend Blockly-like authoring | +| `STORY_RUNTIME_API_JSON_CONTRACT.md` | Runtime API JSON shape | +| `LOCAL_AI_STUDIO_SPEC.md` | Local AI studio integration | +| `QA_TEST_MATRIX_SPEC.md` | QA matrix + acceptance criteria | + +## Editing Rules + +- A spec change is a contract change. List affected consumers in the PR description. +- Bump the version header at the top of the file when altering existing fields; never silently rename keys. +- New spec docs must be linked from root `CLAUDE.md` "Canonical Files" if they introduce a new contract. +- Pair runtime/frontend spec changes — the matching `tools/scenario/compile_runtime3.py` and `frontend-scratch-v2/src/lib/runtime3.ts` updates ship in the same PR. + +## Anti-Patterns + +- "Implementation detail" comments in specs — if it's not part of the contract, remove it +- Adding fields without specifying optionality, defaults, and version +- Letting the IR drift between Python compiler and TS runtime without updating the spec first +- Documenting hardware pins/ports here — they belong in `ESP32_ZACUS/` firmware sources diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md new file mode 100644 index 0000000..309ae2e --- /dev/null +++ b/tests/CLAUDE.md @@ -0,0 +1,34 @@ +# Tests + +Python unittest suite covering Runtime 3 IR, NPC engine integration, and scenario invariants. + +## Layout + +| File / Dir | Scope | +|------------|-------| +| `test_npc_engine.py` | NPC state machine (triggers, mood, rule firing) | +| `test_npc_integration.py` | NPC ↔ TTS ↔ phrase bank wiring | +| `test_npc_phrases_schema.py` | `npc_phrases.yaml` schema invariants | +| `runtime3/` | Runtime 3 compiler/IR/pivot tests | + +## Running + +```bash +python3 -m unittest discover -s tests # full suite +python3 -m unittest tests.test_npc_engine # single module +make runtime3-test # via Makefile target +``` + +## Patterns + +- Use `unittest.TestCase`, not pytest fixtures (suite is stdlib-only). +- Load YAML fixtures from `game/scenarios/`, never inline them — tests must catch drift in real content. +- For NPC trigger timing tests, inject a fake clock; never rely on `time.sleep`. +- Snapshot the compiled Runtime 3 IR in `runtime3/` tests — diff failures should point at the offending pivot/zone ID. + +## Anti-Patterns + +- Mocking `compile_runtime3.py` output instead of running it on real fixtures +- Tests that pass when YAML IDs are renamed but downstream consumers break +- Importing from `tools/` without going through public functions +- Suppressing UnicodeDecodeError on French content — fix the encoding instead diff --git a/tools/CLAUDE.md b/tools/CLAUDE.md new file mode 100644 index 0000000..1150264 --- /dev/null +++ b/tools/CLAUDE.md @@ -0,0 +1,40 @@ +# Tools + +Python tooling for scenario compilation, validation, voice pipeline, MCP hardware, and dev workflow. + +## Layout + +| Subdir | Purpose | +|--------|---------| +| `scenario/` | Runtime 3 compiler, validator, simulator, pivot verifier, MD/firmware exporters | +| `audio/` | Audio manifest validator | +| `printables/` | Printables manifest validator | +| `tts/` | NPC phrase pool generator (Piper TTS → `hotline_tts/`) | +| `stt/` | Speech-to-text helpers | +| `dev/` | TUI dashboard, MCP hardware server, `zacus.sh` CLI (12 actions) | +| `playtest/` | Playtest harness | +| `setup/`, `repo_state/`, `requirements/`, `images/`, `test/` | Auxiliary | + +## Conventions + +- Every script accepts `--help` (argparse). Run it before invoking with new args. +- Idempotent by default — generators (e.g. `tts/generate_npc_pool.py`) skip already-produced artefacts. Add `--force` flags rather than breaking idempotency. +- Scripts run from repo root; use repo-relative paths via `pathlib.Path(__file__).resolve().parents[N]`. +- All scenario tools share `scenario/runtime3_common.py` for IR types — extend it, don't redefine types per script. + +## Validate Changes + +```bash +python3 -m py_compile +python3 tools/scenario/.py --help +make all-validate # if scenario tools changed +python3 -m unittest discover -s tests # if NPC/runtime touched +``` + +## Anti-Patterns + +- Hardcoding machine-specific paths/ports (Tower IP `192.168.0.120` is acceptable but should be overridable via env) +- Requiring chat interaction when a local prompt/script wait suffices +- Diverging IR shape between `compile_runtime3.py` and `runtime3_common.py` +- Adding new validators without wiring them into `Makefile` `all-validate` +- Writing to `game/scenarios/exports/` directly — only `export_md.py` does that