Files
Claude Worker claude2 27544fecd1 feat(blocks): scene/puzzle blocks in atelier
Catégorie « Énigmes » dans le catalogue Blockly canonique :
zacus_scene (écran maître title/subtitle/symbol/effect),
zacus_puzzleQR et zacus_puzzleSound. L'export blocks_to_runtime3
les attache au step courant comme objets IR scene/puzzle (pas des
actions), avec clamping/troncature vers les limites strictes du
gateway pour que l'IR émis valide toujours. Copie hub régénérée
via sync_blockly_catalog.sh. 14 tests d'export ajoutés (60 verts).
2026-06-11 03:34:21 +02:00

8.1 KiB
Raw Permalink Blame History

Zacus Runtime 3 Specification

Goal

Define a portable runtime artifact that can be compiled from YAML or Blockly-authored content and executed by firmware or simulated locally.

Design Principles

  • Narrative canon remains in YAML during migration.
  • Runtime IR is deterministic, versioned, and JSON serializable.
  • Firmware consumes Runtime 3 but does not own narrative semantics.
  • Import from legacy canonical YAML is allowed in a linear_import migration mode.

IR Shape

{
  "schema_version": "zacus.runtime3.v1",
  "scenario": {
    "id": "ZACUS_V2",
    "version": 3,
    "title": "Le mystère du Professeur Zacus",
    "entry_step_id": "STEP_U_SON_PROTO",
    "source_kind": "yaml"
  },
  "steps": [
    {
      "id": "STEP_U_SON_PROTO",
      "scene_id": "SCENE_U_SON_PROTO",
      "audio_pack_id": "",
      "actions": [],
      "apps": [],
      "transitions": []
    }
  ],
  "metadata": {
    "migration_mode": "native",
    "generated_by": "zacus_runtime3"
  }
}

Transition Model

  • event_type: button, serial, timer, audio_done, unlock, espnow, action
  • event_name: opaque token preserved as runtime contract
  • target_step_id: required
  • priority: integer, lower first
  • after_ms: only meaningful for timer-style transitions

Migration Modes

  • native: authored directly for Runtime 3 with explicit transitions
  • linear_import: derived from the legacy canonical YAML using ordered steps when no explicit graph exists

Execution Model

  • Runtime loads one IR document.
  • Entry step is resolved from scenario.entry_step_id.
  • A simulator can replay deterministic transition events without hardware.
  • Firmware adapters may enrich steps with board-specific actions, but not mutate story flow semantics.

Action Shape

Actions are emitted by authoring tools (blocks studio, firmware import, hand-written YAML) and consumed by runtime adapters. Two equivalent shapes are accepted:

  • String form (legacy firmware import): "led_pattern:rainbow" — opaque, runtime parses at its discretion.
  • Dict form (preferred for new authoring): { "kind": "<name>", ...payload }.

Standard action kinds

kind Payload Semantics
tts_say text: string Speak text via the active TTS backend
wait_user_voice timeout_ms: int Block until utterance end or timeout
hw_servo channel: int, angle: int Set servo on channel to angle degrees
qr_expect value: string Arm the QR matcher with expected payload
led_pattern pattern: string Play a named LED pattern
sound_play asset: string Play an asset from the media manager
score_add delta: int Mutate the run-scoped score variable
set_var name: string, value: string Set a scenario variable
condition expr: string, then: [action,…], else: [action,…] Standalone branching. Evaluate expr; execute the then action list if truthy, else otherwise. Nested condition actions are allowed.

condition action

The condition action keeps branching local to the current step — no extra transitions or step duplication needed for simple if/else logic. The runtime evaluates expr against the current variable scope (left intentionally loose; runtimes that don't implement evaluation MAY treat unknown expr as falsy and log it).

Authoring tools that produce condition actions (e.g. BlockKind.logicIf in the Blocks Studio) MUST place all branch-local actions inside then / else. Use a transition (event_type action, event_name goto:<target>) when you need to jump to another step, not a condition.

Example:

{
  "kind": "condition",
  "expr": "score >= 3",
  "then": [
    { "kind": "tts_say", "text": "Bravo, niveau suivant." },
    { "kind": "set_var", "name": "unlocked_act2", "value": "true" }
  ],
  "else": [
    { "kind": "tts_say", "text": "Encore un effort." }
  ]
}

Step extras: puzzle and scene (master local puzzles + display)

Steps MAY carry two optional objects, passed through verbatim by the compiler and consumed by the ESP32 master firmware (ESP32_ZACUS/idf_zacus, contract mirrored in components/game_endpoint/include/puzzle_binding.h). Validation is STRICT at the gateway/compiler; the firmware re-validates leniently.

puzzle — local puzzle arming (P1 sound / P3 QR)

Armed when the master enters the step (POST /game/step). On solve, the fragment digits are reported into the master's assembled code.

{ "id": 3, "type": "qr",
  "codes": ["zacus-qr-1", "zacus-qr-2"],
  "fragment": [5] }

{ "id": 1, "type": "sound",
  "melody": [60, 62, 64, 65], "tolerance": 1,
  "fragment": [1, 2] }
  • id: int 1..8 (puzzle slot in the master's puzzle_state).
  • type: qr (camera + ordered scan) or sound (mic + melody).
  • codes (qr): 1..16 strings, each < 32 chars, scanned in order.
  • melody (sound): 1..32 MIDI notes 0..127; tolerance: int >= 0 semitones (default 1).
  • fragment: 1..4 decimal digits (ints 0..9) contributed to the code.

scene — master display metadata

Drives the master's on-device scene view (title/subtitle/symbol + effect). All fields optional.

{ "title": "MISSION QR", "subtitle": "scannez dans l'ordre",
  "symbol": "RUN", "effect": "gyro" }
  • title <= 47 chars, subtitle <= 63, symbol <= 15.
  • effect: pulse (default) | glitch | gyro | none.

État de l'authoring et chaînon manquant

Au stade actuel, les champs puzzle/scene sont portés de bout en bout côté exécution : le compilateur (runtime3_common.py) les transmet verbatim de l'IR au firmware, qui arme les énigmes locales (P1 son / P3 QR) et pilote l'écran. Un IR de référence exerçant ces champs est fourni : hardware/firmware/data/story/runtime3/EXAMPLE_LOCAL_PUZZLES.json (validé par validate_runtime3_document, testé en bout-à-bout via POST /game/scenario puis POST /game/step).

Le pont authoring → firmware est désormais construit. Le compilateur traduit le vocabulaire d'authoring des puzzles: (blocs solution:) vers les objets puzzle de l'IR et les attache au step correspondant (par screen_scene_id == puzzle.scene). Fonctions : translate_solution_to_puzzle et _attach_puzzles_from_authoring dans runtime3_common.py.

Table de correspondance solution:puzzle: :

Authoring (type + solution) IR puzzle
qr + qr_payload: "X" type:qr, codes:["X"]
qr + qr_sequence: [a,b] type:qr, codes:[a,b] (ordre conservé)
audio_frequency + frequency_hz (+ tolerance_hz) type:sound, melody:[hz→MIDI], tolerance (≈ semitons, ≥1)
sound/sequence + solution.notes: [MIDI…] type:sound, melody:[…]

Règles : le slot firmware id vient de puzzle_id sinon d'un défaut par type (sound→1, sequence→2, qr→3) ; le fragment (14 chiffres) vient de code_fragment sinon vaut le chiffre de l'id. Une énigme non traduisible (ex. sequence de lettres sans notes MIDI — une suite de lettres n'est pas une séquence de hauteurs détectable) est ignorée avec un avertissement, sans casser la compilation du scénario. Un step portant déjà un puzzle explicite n'est pas écrasé (le passthrough direct prime).

Côté atelier Blockly, la catégorie « Énigmes » expose trois blocs step-level (catalogue frontend-v3/packages/shared/blockly/zacus_blocks.mjs, export tools/zacus-gateway/blocks_to_runtime3.py) : zacus_scene (title/subtitle/symbol/effect → objet scene), zacus_puzzleQR (slot, codes ordonnés séparés par virgules, fragment) et zacus_puzzleSound (slot, mélodie MIDI séparée par virgules, tolérance, fragment) → objet puzzle. Ces blocs n'émettent pas d'actions : ils s'attachent au step courant (un de chaque par step, le dernier gagne avec avertissement) ; l'export tronque/borne les valeurs hors limites (avec avertissement) pour que l'IR émis passe toujours la validation stricte. Tests : tests/runtime3/test_blocks_export_puzzle_scene.py. EXAMPLE_LOCAL_PUZZLES.json reste l'IR de référence le plus complet (puzzle + scene par step).