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).
This commit is contained in:
L'électron rare
2026-05-01 11:44:17 +02:00
parent d88a53eb67
commit d31d8af180
8 changed files with 328 additions and 52 deletions
+49 -52
View File
@@ -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/`
+45
View File
@@ -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
+47
View File
@@ -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
+48
View File
@@ -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/<app>/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
+34
View File
@@ -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
+31
View File
@@ -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
+34
View File
@@ -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
+40
View File
@@ -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 <changed_files>
python3 tools/scenario/<changed_script>.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