Context:
The six age-11 stories were short (12-24 passages each, 87 total). We
want much deeper, more replayable branching adventures — roughly ten
times the passages.
Approach:
Rewrite each story in place (same titles/heroes/universe, same library
of six tiles), greatly expanding the world with new locations,
characters and branches. One book per file, authored in parallel, then
every graph validated independently.
Changes (passages before -> after):
- dragon_flamme 24 -> 131 (14 endings)
- ecole_sorciers 13 -> 137 (10)
- hamster_disparu 12 -> 132 (13)
- planete_zigzag 12 -> 131 (11)
- tresor_caramel 13 -> 151 (10)
- voyage_dinos 13 -> 133 (10)
- Total 87 -> 815 passages, 68 endings, ~1928 choices.
Every graph validated: YAML loads, single start, no broken gotos, every
passage reachable, <=4 choices/passage, screen<=40 & label<=38 chars,
gentle kid-safe endings (failures loop back or end softly).
Impact:
Far richer, replayable stories for the boot library. Text only here —
regenerate/stage WAVs with tools/gamebook/build_gamebook.py when the
Freenove is back online (currently unplugged).
Context:
The six age-11 stories were short and a bit dry (one or two sentences
per passage). We want richer, more immersive storytelling for a young
reader/listener.
Approach:
Rewrite all six in place (same filenames, library stays six tiles),
keeping the age-11 tone and gentle "oops, try again" endings, but with
much fuller prose: scene-setting, dialogue, a little character and
emotion, and deeper choice trees with more real branches.
Changes:
- dragon_flamme: ~600 -> ~2050 words, 24 passages, 4 endings.
- ecole_sorciers, hamster_disparu, planete_zigzag, tresor_caramel,
voyage_dinos: each roughly doubled (~1050-1260 words), 12-13
passages, 2-3 endings.
- All graphs validated: no broken gotos, every passage reachable.
Impact:
The boot library now tells proper little stories (the WAV narration
reads the full text; the screen shows the opening of each passage).
Text only here — rebuild/stage WAVs with the generator to deploy.
Context:
The gamebook tiles were filled with fairly dark adventures (haunted
manor, deaths, being eaten/frozen) — not suitable for an 11-year-old.
We want the boot library to be age-appropriate.
Approach:
Write six light, branching stories pitched at ~11 years (adventure +
humour, gentle "oops, try again" failures, never grim), and move the
six mature adventures into a non-library subfolder so they're kept but
out of the kids' picker (the generator globs game/gamebooks/*.yaml,
non-recursive). Text only — no WAV synthesis here.
Changes:
- New kid stories in game/gamebooks/: dragon_flamme, tresor_caramel,
planete_zigzag, hamster_disparu, ecole_sorciers, voyage_dinos
(11-17 passages each, 2-5 endings, all reachable, no broken gotos).
- Move the 6 mature adventures to game/gamebooks/aventures/ (archived,
excluded from the library glob).
Impact:
The 6-tile boot library now offers age-appropriate stories. Build/stage
them with tools/gamebook/build_gamebook.py when ready (not done here).
Context:
The gamebook engine had a single short demo. We want several long,
text-only "livre dont vous etes le heros" adventures to play on the
Freenove (and to exercise deeper branching).
Approach:
Author each as a standalone gamebook YAML in game/gamebooks/, using the
navigable format (passages with screen/text, choices as label+goto;
a choiceless passage is an ending that restarts on click).
Changes:
- game/gamebooks/tour_horloger.yaml: "La Tour de l'Horloger"
(fantastique/temps) — 23 passages, 8 fins.
- game/gamebooks/train_de_nuit.yaml: "Le Dernier Train de Nuit"
(mystere/huis clos) — 19 passages, 7 fins.
- game/gamebooks/ile_echos.yaml: "L'Ile aux Mille Echos"
(aventure/survie) — 16 passages, 7 fins.
Impact:
Three richly branched stories ready to stage on the master SD with
tools/gamebook/build_gamebook.py. Graphs validated: no broken gotos,
every passage reachable, multiple endings each.
Context:
The new firmware gamebook mode needs a book on the Freenove SD, plus a
reusable way to author one. We also want a playable demo to prove the
whole chain (button -> navigation -> WAV narration from SD -> screen).
Approach:
Author the book in a human-friendly YAML; a generator renders each
passage's narration with macOS say (16 kHz mono, canonical WAV — the
Apple FLLR chunk is stripped so the firmware parser decodes it),
emits a compact gamebook.json, and stages WAVs + JSON onto the master
SD via POST /game/file?path=sd/gamebook/.
Changes:
- game/gamebooks/zacus_demo.yaml: a branching mini-adventure (the
Professeur Zacus lab) — 8 passages, 3 endings.
- tools/gamebook/build_gamebook.py: reusable/idempotent generator
(--book/--ip/--sd-dir/--dry-run); ASCII-folds on-screen text (fonts
have no accents) while the WAV keeps the accented narration; choices
carry label + goto (navigation is generic, D-pad + click).
- .gitignore: ignore the generated WAV pack (tools/gamebook/build/).
- Bump ESP32_ZACUS to the gamebook-mode firmware.
Impact:
Authors can write a gamebook in YAML and stage it to the Freenove in
one command; the bundled demo plays end to end, fully offline.
The phone now rings on cue from the game with NO firmware change. A
call_on_scene map (phone_directory.yaml: {SCENE_X: number}) tells the
gateway which scenes are scripted incoming calls; since the runtime
already pushes the scene to /game/step, the gateway auto-rings the PLIP
with that NPC on scene entry (deduped, once per entry). Factored a
_ring_board helper (shared by /game/call + auto-ring). Map ships empty
(no surprise rings) with commented examples for the designer.
Les puzzles de zacus_v3.yaml n'avaient ni `act`, ni `objective`, ni
`firmware_step` (requis par le validateur schema V2). Ajout calqué sur
zacus_v2 : Diapason→acte 1, Piano LEFOU→acte 2, QR Archives→acte 2 ;
valeurs déduites du contenu existant (scène, runtime_steps, solution).
Le scénario valide, compile et simule désormais le playthrough complet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les guillemets typographiques servaient de pseudo-délimiteurs YAML :
les valeurs chargées contenaient littéralement “…” (endpoints LLM/TTS,
names des puzzles) et la séquence du piano valait [“L”,“E”,…] au lieu
de [L,E,F,O,U]. Conversion “”→", ‘’→', …→... ; tirets et « » français
conservés. Parse vérifié avant/après (yaml.safe_load).
Server-side fuzzy alias table applied to the final STT transcript
before forwarding to /voice/intent. Closes the metier-vocabulary
gap of generic STT models (U-SON, LA 440 Hz, Zacus, ...) without
needing per-request prompt biasing — which moshi-server's Rust
runtime does not expose anyway.
Loader is soft on pyyaml (degrades to no-op if missing) and on
the yaml file itself (logs + skips on parse error). Patterns are
case-insensitive with auto word-boundaries, with a (?...) escape
hatch for advanced regex.
Per-puzzle entries layer on top of globals — firmware passes
puzzle_id in the hello frame; legacy clients omit it and get
just the global polish. Loaded once at module import; restart
voice-bridge to reload.
Unit tested on 5 representative transcripts (eu son nez -> U-SON,
diapazon -> diapason, le rasus -> Zacus, le la 440 hertz -> LA 440 Hz).
Adds slice D_P4 of the hints engine: an adaptive level *floor* derived
from the group profile (TECH / NON_TECH / MIXED / BOTH), the elapsed
stuck-time, and the per-puzzle failed-attempt counter. The floor is
combined at the call site as
level_served = max(level_requested, count + 1, level_floor_adaptive)
and clamped to NPC_MAX_HINT_LEVEL.
Configuration lives in game/scenarios/hints_adaptive.yaml (override
via HINTS_ADAPTIVE_PATH). When the file is absent, adaptive mode is
silently disabled (warning logged) so the engine keeps running with
the existing P3 logic.
Adds two firmware-driven endpoints:
- POST /hints/puzzle_start seeds puzzle_started_at_ms (idempotent,
with optional force=true to restart)
- POST /hints/attempt_failed bumps failed_attempts_for_puzzle, 404s
on unknown puzzle_id
/hints/ask gains an optional group_profile field and now reports
level_floor_adaptive, stuck_minutes, failed_attempts and
group_profile_used in every response (refusal envelope included).
Auto-trigger on stuck-timer remains firmware-side (slice D_P6) — this
slice does not poll, only serves higher levels when asked.
Tests: 35 / 35 green (26 P1+P2+P3 unchanged + 9 new). Backward
compatible by construction: when group_profile is omitted MIXED is
used and at t=0 the floor is 0, so the existing P3 decision stands.
Phase P1 of the playtest harness design spec:
* tools/playtest/run_playtest.py — replays a playtest YAML
against the Runtime 3 IR and compares to (or updates) a
snapshot JSON
* game/scenarios/playtests/zacus_v3_60min_tech.playtest.yaml
— first canonical playtest, resolves PUZZLE_LA_440 via the
UNLOCK_LA_440 unlock event
* game/scenarios/playtests/snapshots/
zacus_v3_60min_tech.snapshot.json — committed reference
* Makefile: new "playtest" target wiring the runner via uv
Critical-asserts evaluator and TS runner are out of P1 scope.
Add static personality prompt file for Professor Zacus with moods,
communication rules and in-game limits. Voice bridge now accepts
POST /game_state from ESP32 to inject dynamic scene context into the
LLM system prompt (scene, elapsed time, hints, mood, objective).
- npc_phrases.yaml: full phrase bank (hints, personality,
adaptation, bridges, false leads, ambiance) in French
- generate_npc_pool.py: batch TTS generator (Piper API)
- test_npc_engine.py: 21 unit tests for trigger logic
- test_npc_phrases_schema.py: YAML schema validation (12 tests)
- Fix FAST_PROGRESS guard: use absolute time limit to prevent
false positives after several minutes on a scene
Impact: Sprint 3 NPC data layer complete. 33 tests passing.
- Updated `validate_scenario.py` to handle V2 scenario YAML files, introducing new required keys and validation logic.
- Added support for legacy V1 scenarios with an option to allow migration.
- Modified `run_content_checks.sh` to validate V2 scenarios and associated audio and printables manifests.
- Introduced SceneGyrophareConfig structure for configuration parameters.
- Implemented SceneGyrophare class with methods for creating, destroying, and updating the gyrophare effect.
- Added rendering functions for background, beams, and warning triangle.
- Integrated timer for animation updates based on frame rate.
- Created a log file for ESP32 audio kit boot information.
- Removed 9 unused screen JSON files: SCENE_BROKEN, SCENE_CAMERA_SCAN, SCENE_FIREWORKS, SCENE_LA_DETECT, SCENE_MEDIA_ARCHIVE, SCENE_SIGNAL_SPIKE, SCENE_WIN, SCENE_WINNER, SCENE_WIN_ETAPE.
- Updated AGENT_TODO.md to reflect the cleanup actions and results.
- Enhanced StorageManager to handle SD card access failures with a failure streak mechanism, switching to LittleFS after repeated failures.
- Added support for aliasing long action IDs to short filenames in the generator.
- Improved scenario manager to handle different key names for action IDs in YAML files.
- Updated multiple scene JSON files to improve visual themes, effects, and transitions.
- Changed effect speeds and added missing timeline objects for better scene management.
- Enhanced SCENE_LA_DETECTOR, SCENE_LEFOU_DETECTOR, SCENE_MEDIA_MANAGER, SCENE_QR_DETECTOR, SCENE_WARNING, SCENE_WIN_ETAPE1, and SCENE_WIN_ETAPE2 with refined titles, subtitles, and symbols.
- Introduced a new user live logger script to capture serial logs during real user playtests for better debugging and analysis.
- Added a workbench export/import tool to streamline scene editing and synchronization with JSON files.
- Improved FX verificator documentation to clarify usage modes and verification processes.
* docs: import zacus conversation bundle v3 and close ZIP intake TODO
* tools: add reproducible validator installer and document next gates
* docs: remove ZIP terminology and align intake docs to conversation bundle
* tools: add runtime bundle validator and update docs workflow
* feat: promote gameplay scenario and enforce G3 bundle checks in CI
* docs: add deep repo analysis and next-step TODOs
* Initial plan (#107)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
- Implemented QrUnlockApp class with methods for lifecycle management (begin, start, update, stop, handleEvent) and snapshot retrieval.
- Created header file for QrUnlockApp with necessary method declarations and private members.
- Added Yamale schema for scenario template to define prompt input and optional firmware snapshot.