848 lines
46 KiB
JavaScript
848 lines
46 KiB
JavaScript
// Canonical Zacus Blockly catalog — single source of truth for BOTH editors:
|
||
// - Atelier webapp: import { registerZacusBlocks } from '@zacus/shared/blockly'
|
||
// - zacus-hub WebView: apps/zacus-hub/Resources/blockly/zacus_blocks.js is
|
||
// GENERATED from this file by tools/dev/sync_blockly_catalog.sh (strips
|
||
// `export` keywords and appends the legacy window.* globals + auto-register).
|
||
// Never edit the hub copy by hand.
|
||
//
|
||
// Block kinds match tools/zacus-gateway/blocks_to_runtime3.py (without the
|
||
// `zacus_` prefix used for the Blockly type names). Shapes follow Scratch
|
||
// conventions: hat (sceneStart), cap (sceneEnd), statement (most), c-block
|
||
// (logicIf with "alors"/"sinon" slots).
|
||
|
||
export const ZACUS_COLOR = {
|
||
scene: "#5085F2",
|
||
npc: "#D85B73",
|
||
audio: "#A772D9",
|
||
lcd: "#338CBF",
|
||
hardware: "#2EAD8C",
|
||
espnow: "#F28033",
|
||
box3: "#8C5A33",
|
||
m5: "#4D73A6",
|
||
plip: "#C74D8C",
|
||
logic: "#F2A633",
|
||
puzzle: "#59A86C",
|
||
operator: "#59A659",
|
||
control: "#D9A032",
|
||
gm: "#7A5CCC",
|
||
};
|
||
|
||
// Vague 1 — opérateurs de comparaison (sérialisés en string d'expression
|
||
// dans la grammaire « lâche » déjà utilisée par les conditions du scénario,
|
||
// ex `score > 5`). value = symbole émis dans la string.
|
||
export const ZACUS_COMPARE_OPS = [
|
||
["=", "=="], ["≠", "!="], ["<", "<"], ["≤", "<="], [">", ">"], ["≥", ">="],
|
||
];
|
||
export const ZACUS_BOOL_OPS = [
|
||
["et", "and"], ["ou", "or"],
|
||
];
|
||
export const ZACUS_ARITH_OPS = [
|
||
["+", "+"], ["−", "-"], ["×", "*"], ["÷", "/"],
|
||
];
|
||
|
||
// Effets d'affichage de la vue scène du maître (puzzle_binding.h).
|
||
export const ZACUS_SCENE_EFFECTS = [
|
||
["pulse", "pulse"],
|
||
["glitch", "glitch"],
|
||
["gyro", "gyro"],
|
||
["aucun", "none"],
|
||
];
|
||
|
||
// « Zacus dit » voice options. `voice` maps to the voice-bridge `voice_ref`
|
||
// (reference clip for F5-TTS voice cloning); emotion + rate are stored in the
|
||
// IR (tts_say) as forward-compat fields — the bridge ignores them until it
|
||
// learns them.
|
||
export const ZACUS_VOICES = [
|
||
["Zacus", "zacus"],
|
||
["Narrateur", "narrateur"],
|
||
["Assistant", "assistant"],
|
||
];
|
||
export const ZACUS_EMOTIONS = [
|
||
["neutre", "neutral"],
|
||
["joyeux", "happy"],
|
||
["inquiet", "worried"],
|
||
["mystérieux", "mysterious"],
|
||
["pressé", "urgent"],
|
||
];
|
||
|
||
// ── Vague 2 : couche « Maître du jeu » (game-master adaptatif) ─────────
|
||
// Vocabulaire repris de game/scenarios/zacus_v3_complete.yaml (phases,
|
||
// npc_mood_rules, hint_escalation, scoring conditionnel, group_classification).
|
||
|
||
// Déclencheurs de phase (phases.*.trigger). game_start est le seul présent
|
||
// dans le scénario ; les autres couvrent l'enchaînement profiling→outro.
|
||
export const ZACUS_PHASE_TRIGGERS = [
|
||
["démarrage partie", "game_start"],
|
||
["phase précédente finie", "phase_done"],
|
||
["profilage terminé", "profiling_done"],
|
||
["manuel", "manual"],
|
||
];
|
||
|
||
// Humeurs NPC adaptatives (npc_mood_rules.*.mood).
|
||
export const ZACUS_NPC_MOODS = [
|
||
["neutre", "NEUTRAL"],
|
||
["impressionné", "IMPRESSED"],
|
||
["inquiet", "WORRIED"],
|
||
["pressé", "URGENT"],
|
||
["amusé", "AMUSED"],
|
||
];
|
||
|
||
// Actions adaptatives (npc_mood_rules.*.action + game-master loop).
|
||
export const ZACUS_GM_ACTIONS = [
|
||
["indice proactif", "proactive_hint"],
|
||
["passer au suivant", "skip_to_next"],
|
||
["ajouter énigme bonus", "add_bonus_puzzle"],
|
||
];
|
||
|
||
// Types de pénalité de score (scoring.*).
|
||
export const ZACUS_PENALTY_KINDS = [
|
||
["par minute", "time_penalty_per_minute"],
|
||
["par indice", "hint_penalty"],
|
||
];
|
||
|
||
// ── Vagues 3 & 4 : énigmes manquantes + assemblage du code final ──────
|
||
// Vocabulaire repris de game/scenarios/zacus_v3_complete.yaml :
|
||
// - P2 Circuit LED, P4 Fréquence Radio, P5 Code Morse, P6 Symboles NFC,
|
||
// P7 Coffre Final (puzzles.*),
|
||
// - phases.climax.code_assembly (mapping puzzle→position de chiffres).
|
||
|
||
// Mode du décodage morse (P5.difficulty_variants : full_morse_decode vs
|
||
// morse_light_mode où l'émission est lumineuse au lieu d'être sonore).
|
||
export const ZACUS_MORSE_MODES = [
|
||
["sonore (normal)", "normal"],
|
||
["lumineux (light)", "light"],
|
||
];
|
||
|
||
// ── Phase D2 : binding carte ──────────────────────────────────────────
|
||
// Cartes connues = clés de tools/zacus-gateway/boards.yaml. Le champ `board`
|
||
// (défaut `master`) sur les énigmes réelles et le bloc contexte `hwOnBoard`
|
||
// ciblent l'exécution sur une carte précise. Garde cette liste en miroir de
|
||
// VALID_BOARDS côté Python (tools/scenario/runtime3_common.py) et de boards.yaml.
|
||
export const ZACUS_BOARDS = [
|
||
["Master", "master"],
|
||
["BOX-3", "box3"],
|
||
["PLIP", "plip"],
|
||
["Coffre P7", "p7_coffre"],
|
||
];
|
||
|
||
export function registerZacusBlocks(Blockly) {
|
||
function def(name, init) { Blockly.Blocks[name] = { init: init }; }
|
||
function stack(t, color) { t.setPreviousStatement(true, null); t.setNextStatement(true, null); t.setColour(color); }
|
||
function dropdown(opts) {
|
||
// accept [["label","value"], …] or ["value", …]
|
||
return new Blockly.FieldDropdown(opts.map(o => Array.isArray(o) ? o : [o, o]));
|
||
}
|
||
// Blockly >=10 ships FieldMultilineInput as a plugin, not core. Fall back to
|
||
// a plain text input so the catalog works against both bundles.
|
||
const MultilineInput = Blockly.FieldMultilineInput || Blockly.FieldTextInput;
|
||
|
||
// Scènes
|
||
def("zacus_sceneStart", function () {
|
||
this.appendDummyInput().appendField("▶ Début de scène").appendField(new Blockly.FieldTextInput("intro"), "id");
|
||
this.setNextStatement(true, null); this.setColour(ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_sceneEnd", function () {
|
||
this.appendDummyInput().appendField("⏹ Fin de scène");
|
||
this.setPreviousStatement(true, null); this.setColour(ZACUS_COLOR.scene);
|
||
});
|
||
def("zacus_sceneGoto", function () {
|
||
this.appendDummyInput().appendField("Aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_sceneBranch", function () {
|
||
// condition : soit un reporter branché (appendValueInput "condition"),
|
||
// soit le champ texte de repli (rétrocompat strings opaques).
|
||
this.appendValueInput("condition").setCheck(null).appendField("Branche si");
|
||
this.appendDummyInput().appendField("ou texte").appendField(new Blockly.FieldTextInput("score > 5"), "condition");
|
||
this.appendDummyInput().appendField("alors").appendField(new Blockly.FieldTextInput("scene_a"), "ifTrue");
|
||
this.appendDummyInput().appendField("sinon").appendField(new Blockly.FieldTextInput("scene_b"), "ifFalse");
|
||
stack(this, ZACUS_COLOR.scene);
|
||
});
|
||
|
||
// NPC — « Zacus dit » porte la voix (voice_ref), l'émotion et la vitesse.
|
||
def("zacus_npcSay", function () {
|
||
this.appendDummyInput().appendField("Zacus dit")
|
||
.appendField(new MultilineInput("Bonjour, je suis le professeur Zacus."), "text");
|
||
this.appendDummyInput().appendField("voix").appendField(dropdown(ZACUS_VOICES), "voice")
|
||
.appendField("émotion").appendField(dropdown(ZACUS_EMOTIONS), "emotion")
|
||
.appendField("vitesse").appendField(new Blockly.FieldNumber(1, 0.5, 2, 0.1), "rate");
|
||
stack(this, ZACUS_COLOR.npc);
|
||
});
|
||
def("zacus_npcWaitResponse", function () {
|
||
this.appendDummyInput().appendField("Attendre réponse — timeout")
|
||
.appendField(new Blockly.FieldNumber(10, 1, 120, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.npc); this.setInputsInline(true);
|
||
});
|
||
def("zacus_npcIntentMatch", function () {
|
||
this.appendDummyInput().appendField("Si intent =").appendField(new Blockly.FieldTextInput("yes"), "intent");
|
||
this.appendDummyInput().appendField("alors aller à").appendField(new Blockly.FieldTextInput("scene_x"), "then");
|
||
stack(this, ZACUS_COLOR.npc);
|
||
});
|
||
|
||
// Audio
|
||
def("zacus_hwSoundPlay", function () {
|
||
this.appendDummyInput().appendField("🔊 Jouer son").appendField(new Blockly.FieldTextInput("sting_win"), "asset");
|
||
stack(this, ZACUS_COLOR.audio); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwAudioStop", function () {
|
||
this.appendDummyInput().appendField("⏸ Stopper l'audio");
|
||
stack(this, ZACUS_COLOR.audio);
|
||
});
|
||
def("zacus_hwAudioVolume", function () {
|
||
this.appendDummyInput().appendField("Volume")
|
||
.appendField(new Blockly.FieldNumber(70, 0, 100, 1), "level").appendField("/100");
|
||
stack(this, ZACUS_COLOR.audio); this.setInputsInline(true);
|
||
});
|
||
// Phase D3 — média réel SD : joue un fichier par CHEMIN SD réel (ex.
|
||
// zacus/<board>/<file>.wav), résolu par le firmware (/sdcard/… ou
|
||
// /littlefs/…). Le `board` vient du contexte hwOnBoard (défaut master),
|
||
// PAS d'un champ ici. Compile → action sound_play{path, board, loop}. Le
|
||
// legacy zacus_hwSoundPlay(asset opaque) reste inchangé.
|
||
def("zacus_mediaPlay", function () {
|
||
this.appendDummyInput().appendField("♫ Jouer média")
|
||
.appendField(new Blockly.FieldTextInput("zacus/master/intro.wav"), "path");
|
||
this.appendDummyInput().appendField("boucle").appendField(new Blockly.FieldCheckbox("FALSE"), "loop");
|
||
stack(this, ZACUS_COLOR.audio); this.setInputsInline(true);
|
||
});
|
||
|
||
// LCD
|
||
def("zacus_hwLCDText", function () {
|
||
this.appendDummyInput().appendField("LCD ligne")
|
||
.appendField(new Blockly.FieldNumber(0, 0, 32, 1), "line")
|
||
.appendField("texte").appendField(new Blockly.FieldTextInput("Bienvenue"), "text");
|
||
stack(this, ZACUS_COLOR.lcd); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwLCDClear", function () {
|
||
this.appendDummyInput().appendField("LCD — effacer");
|
||
stack(this, ZACUS_COLOR.lcd);
|
||
});
|
||
def("zacus_hwLCDImage", function () {
|
||
this.appendDummyInput().appendField("LCD image").appendField(new Blockly.FieldTextInput("splash"), "asset");
|
||
stack(this, ZACUS_COLOR.lcd); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwLCDTouchWait", function () {
|
||
this.appendDummyInput().appendField("Attendre tap zone").appendField(new Blockly.FieldTextInput("btn_yes"), "zone")
|
||
.appendField("timeout").appendField(new Blockly.FieldNumber(30, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.lcd); this.setInputsInline(true);
|
||
});
|
||
|
||
// Hardware (ESP32 générique)
|
||
def("zacus_hwServo", function () {
|
||
this.appendDummyInput().appendField("Servo canal")
|
||
.appendField(new Blockly.FieldNumber(0, 0, 15, 1), "channel")
|
||
.appendField("→ angle").appendField(new Blockly.FieldNumber(90, 0, 180, 1), "angle").appendField("°");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwReadQR", function () {
|
||
this.appendDummyInput().appendField("QR — attendu").appendField(new Blockly.FieldTextInput("ZAC-A1"), "expected");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwLEDPattern", function () {
|
||
this.appendDummyInput().appendField("LED motif").appendField(dropdown([
|
||
["arc-en-ciel","rainbow"],["clignote","blink"],["fondu","fade"],["éteindre","off"]
|
||
]), "pattern");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwBuzzerTone", function () {
|
||
this.appendDummyInput().appendField("Buzzer")
|
||
.appendField(new Blockly.FieldNumber(2000, 20, 20000, 1), "freq").appendField("Hz pendant")
|
||
.appendField(new Blockly.FieldNumber(120, 1, 10000, 1), "ms").appendField("ms");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwRelay", function () {
|
||
this.appendDummyInput().appendField("Relais canal")
|
||
.appendField(new Blockly.FieldNumber(0, 0, 15, 1), "channel")
|
||
.appendField(dropdown([["allumer","on"],["éteindre","off"],["impulsion","pulse"]]), "state");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwSensorRead", function () {
|
||
this.appendDummyInput().appendField("Lire capteur").appendField(new Blockly.FieldTextInput("A0"), "pin")
|
||
.appendField("→").appendField(new Blockly.FieldTextInput("lecture"), "var");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
def("zacus_hwButtonWait", function () {
|
||
this.appendDummyInput().appendField("Attendre bouton").appendField(new Blockly.FieldTextInput("btn_main"), "button")
|
||
.appendField("timeout").appendField(new Blockly.FieldNumber(0, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
|
||
// ESP-NOW
|
||
def("zacus_espnowRegisterPeer", function () {
|
||
this.appendDummyInput().appendField("ESP-NOW alias").appendField(new Blockly.FieldTextInput("annexe1"), "alias")
|
||
.appendField("MAC").appendField(new Blockly.FieldTextInput("AA:BB:CC:DD:EE:FF"), "mac");
|
||
stack(this, ZACUS_COLOR.espnow); this.setInputsInline(true);
|
||
});
|
||
def("zacus_espnowSend", function () {
|
||
this.appendDummyInput().appendField("ESP-NOW → peer").appendField(new Blockly.FieldTextInput("annexe1"), "peer")
|
||
.appendField("cmd").appendField(new Blockly.FieldTextInput("open_door"), "command");
|
||
stack(this, ZACUS_COLOR.espnow); this.setInputsInline(true);
|
||
});
|
||
def("zacus_espnowBroadcast", function () {
|
||
this.appendDummyInput().appendField("ESP-NOW broadcast").appendField(new Blockly.FieldTextInput("reset"), "command");
|
||
stack(this, ZACUS_COLOR.espnow); this.setInputsInline(true);
|
||
});
|
||
def("zacus_espnowWait", function () {
|
||
this.appendDummyInput().appendField("ESP-NOW attendre").appendField(new Blockly.FieldTextInput("ready"), "command")
|
||
.appendField("timeout").appendField(new Blockly.FieldNumber(10, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.espnow); this.setInputsInline(true);
|
||
});
|
||
|
||
// ESP32-S3-BOX-3
|
||
def("zacus_boxIMUShake", function () {
|
||
this.appendDummyInput().appendField("BOX-3 secousse seuil")
|
||
.appendField(new Blockly.FieldNumber(1.5, 0.1, 10, 0.1), "threshold")
|
||
.appendField("g timeout").appendField(new Blockly.FieldNumber(10, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.box3); this.setInputsInline(true);
|
||
});
|
||
def("zacus_boxIRSend", function () {
|
||
this.appendDummyInput().appendField("BOX-3 IR").appendField(dropdown(["NEC","RC5","SONY","RAW"]), "protocol")
|
||
.appendField(new Blockly.FieldTextInput("0x20DF10EF"), "code");
|
||
stack(this, ZACUS_COLOR.box3); this.setInputsInline(true);
|
||
});
|
||
|
||
// M5Stack / M5StickC
|
||
def("zacus_m5Beep", function () {
|
||
this.appendDummyInput().appendField("M5 bip")
|
||
.appendField(new Blockly.FieldNumber(4000, 20, 20000, 1), "freq").appendField("Hz")
|
||
.appendField(new Blockly.FieldNumber(200, 1, 5000, 1), "ms").appendField("ms");
|
||
stack(this, ZACUS_COLOR.m5); this.setInputsInline(true);
|
||
});
|
||
def("zacus_m5LCDText", function () {
|
||
this.appendDummyInput().appendField("M5 LCD").appendField(new Blockly.FieldTextInput("Hello"), "text")
|
||
.appendField(dropdown(["white","yellow","red","green","blue","cyan","magenta"]), "color")
|
||
.appendField("size").appendField(new Blockly.FieldNumber(2, 1, 8, 1), "size");
|
||
stack(this, ZACUS_COLOR.m5); this.setInputsInline(true);
|
||
});
|
||
def("zacus_m5ButtonAB", function () {
|
||
this.appendDummyInput().appendField("M5 bouton").appendField(dropdown(["A","B","C","any"]), "button")
|
||
.appendField("timeout").appendField(new Blockly.FieldNumber(0, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.m5); this.setInputsInline(true);
|
||
});
|
||
def("zacus_m5RGBLed", function () {
|
||
this.appendDummyInput().appendField("M5 LED RGB").appendField(new Blockly.FieldTextInput("#FF8800"), "color");
|
||
stack(this, ZACUS_COLOR.m5); this.setInputsInline(true);
|
||
});
|
||
def("zacus_m5IMUShake", function () {
|
||
this.appendDummyInput().appendField("M5 secousse seuil")
|
||
.appendField(new Blockly.FieldNumber(1.5, 0.1, 10, 0.1), "threshold")
|
||
.appendField("g timeout").appendField(new Blockly.FieldNumber(10, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.m5); this.setInputsInline(true);
|
||
});
|
||
|
||
// PLIP téléphone
|
||
def("zacus_plipRing", function () {
|
||
this.appendDummyInput().appendField("☎ PLIP sonner")
|
||
.appendField(new Blockly.FieldNumber(3, 0, 60, 0.1), "duration_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.plip); this.setInputsInline(true);
|
||
});
|
||
def("zacus_plipPickupWait", function () {
|
||
this.appendDummyInput().appendField("☎ PLIP attendre décroché — timeout")
|
||
.appendField(new Blockly.FieldNumber(30, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.plip); this.setInputsInline(true);
|
||
});
|
||
|
||
// Énigmes locales du maître — ces blocs ne produisent pas d'actions : ils
|
||
// attachent les objets IR `scene` / `puzzle` au step courant (un seul de
|
||
// chaque par step, voir specs/ZACUS_RUNTIME_3_SPEC.md « Step extras »).
|
||
def("zacus_scene", function () {
|
||
this.appendDummyInput().appendField("🖥 Écran maître — titre")
|
||
.appendField(new Blockly.FieldTextInput("MISSION QR"), "title");
|
||
this.appendDummyInput().appendField("sous-titre")
|
||
.appendField(new Blockly.FieldTextInput("scannez dans l'ordre"), "subtitle");
|
||
this.appendDummyInput().appendField("symbole").appendField(new Blockly.FieldTextInput("RUN"), "symbol")
|
||
.appendField("effet").appendField(dropdown(ZACUS_SCENE_EFFECTS), "effect");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleQR", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme QR — slot")
|
||
.appendField(new Blockly.FieldNumber(3, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("codes (dans l'ordre, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("zacus-qr-1, zacus-qr-2"), "codes");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("3"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleSound", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme son — slot")
|
||
.appendField(new Blockly.FieldNumber(1, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("mélodie (notes MIDI, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("60, 62, 64, 65"), "melody")
|
||
.appendField("tolérance").appendField(new Blockly.FieldNumber(1, 0, 12, 1), "tolerance");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("1"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
|
||
// ── Vague 3 : énigmes manquantes (P2/P4/P5/P6/P7) ─────────────────
|
||
// Même pattern que puzzleQR/puzzleSound : step-extra `puzzle` (pas une
|
||
// action), slot id 1-8, paramètres propres à l'énigme + fragment 1-4
|
||
// chiffres. Couleur ZACUS_COLOR.puzzle.
|
||
def("zacus_puzzleLED", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme circuit LED — slot")
|
||
.appendField(new Blockly.FieldNumber(2, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("motif attendu (composants/états, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("R1, LED, SW1"), "pattern");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("3"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleRadio", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme radio — slot")
|
||
.appendField(new Blockly.FieldNumber(4, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("fréquence cible")
|
||
.appendField(new Blockly.FieldNumber(1337, 0, 100000, 1), "frequency").appendField("Hz")
|
||
.appendField("± tolérance").appendField(new Blockly.FieldNumber(10, 0, 10000, 1), "tolerance").appendField("Hz");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("4"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleMorse", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme morse — slot")
|
||
.appendField(new Blockly.FieldNumber(5, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("message")
|
||
.appendField(new Blockly.FieldTextInput("ZACUS"), "message")
|
||
.appendField("mode").appendField(dropdown(ZACUS_MORSE_MODES), "mode");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("5"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleNFC", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme symboles NFC — slot")
|
||
.appendField(new Blockly.FieldNumber(6, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("tags attendus (ordre, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("7, 2, 11, 4, 9, 1, 8, 3, 12, 6, 10, 5"), "tags");
|
||
this.appendDummyInput().appendField("ordre imposé").appendField(new Blockly.FieldCheckbox("TRUE"), "ordered");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("67"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
def("zacus_puzzleCoffre", function () {
|
||
this.appendDummyInput().appendField("🧩 Énigme coffre — slot")
|
||
.appendField(new Blockly.FieldNumber(7, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("longueur du code")
|
||
.appendField(new Blockly.FieldNumber(8, 1, 16, 1), "length").appendField("chiffres");
|
||
this.appendDummyInput().appendField("fragment (1-4 chiffres)")
|
||
.appendField(new Blockly.FieldTextInput("8"), "fragment");
|
||
this.appendDummyInput().appendField("carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
// Bloc matériel : lecture d'un tag NFC hors énigme (catégorie Matériel).
|
||
def("zacus_hwNFCRead", function () {
|
||
this.appendDummyInput().appendField("NFC — attendu").appendField(new Blockly.FieldTextInput("tag_07"), "expected")
|
||
.appendField("→").appendField(new Blockly.FieldTextInput("nfc_uid"), "var")
|
||
.appendField("timeout").appendField(new Blockly.FieldNumber(30, 0, 600, 1), "timeout_s").appendField("s");
|
||
stack(this, ZACUS_COLOR.hardware); this.setInputsInline(true);
|
||
});
|
||
|
||
// ── Vague 4 : assemblage du code final / coffre ───────────────────
|
||
// Actions (pas des step-extras) mappées aux kinds IR code_assembly /
|
||
// coffre_verify / code_fragment. phases.climax.code_assembly.
|
||
// Assemble le code final : longueur totale + mapping puzzle→chiffres
|
||
// (ex « P1_SON:1-2, P2_CIRCUIT:3, P4_RADIO:4 … »).
|
||
def("zacus_codeAssembly", function () {
|
||
this.appendDummyInput().appendField("🔐 Assembler le code — longueur")
|
||
.appendField(new Blockly.FieldNumber(8, 1, 16, 1), "length").appendField("chiffres");
|
||
this.appendDummyInput().appendField("fragments (puzzle:positions, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("P1_SON:1-2, P2_CIRCUIT:3, P4_RADIO:4, P5_MORSE:5, P6_SYMBOLES:6-7, P3_QR:8"), "fragments");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
// Associe explicitement un fragment de puzzle à une (ou des) position(s).
|
||
def("zacus_codeFragment", function () {
|
||
this.appendDummyInput().appendField("↳ Fragment puzzle")
|
||
.appendField(new Blockly.FieldTextInput("P1_SON"), "puzzle")
|
||
.appendField("→ positions").appendField(new Blockly.FieldTextInput("1-2"), "positions");
|
||
stack(this, ZACUS_COLOR.puzzle); this.setInputsInline(true);
|
||
});
|
||
// Vérifie le code assemblé : déclenche l'ouverture / la fin.
|
||
def("zacus_coffreVerify", function () {
|
||
this.appendDummyInput().appendField("✅ Vérifier coffre — slot")
|
||
.appendField(new Blockly.FieldNumber(7, 1, 8, 1), "id");
|
||
this.appendDummyInput().appendField("au succès aller à")
|
||
.appendField(new Blockly.FieldTextInput("outro"), "on_success");
|
||
stack(this, ZACUS_COLOR.puzzle);
|
||
});
|
||
|
||
// ── Phase D2 : binding carte ──────────────────────────────────────
|
||
// Bloc contexte c-block « ▸ sur la carte X { … } » : il ne produit aucune
|
||
// action propre, il TAGUE chaque action de son slot `body` avec le `board`
|
||
// choisi (cf. blocks_to_runtime3.py). previous+next connectés. Réel : le
|
||
// routage gateway s'appuie dessus.
|
||
def("zacus_hwOnBoard", function () {
|
||
this.appendDummyInput().appendField("▸ sur la carte").appendField(dropdown(ZACUS_BOARDS), "board");
|
||
this.appendStatementInput("body").setCheck(null).appendField("faire");
|
||
stack(this, ZACUS_COLOR.hardware);
|
||
});
|
||
|
||
// ── Phase D4 : transitions réelles ────────────────────────────────
|
||
// Blocs « Quand … → aller à [target] » : chacun émet UNE transition avec le
|
||
// vrai event_type firmware-backed (button/serial/audio_done/unlock/espnow).
|
||
// event_type ∈ runtime3_common.EVENT_TYPES (pas d'extension de schéma, juste
|
||
// de l'émission). target = id d'étape cible (même pattern que sceneGoto).
|
||
// Voie RÉELLE : consommés côté gateway/web (le firmware n'a pas de moteur de
|
||
// transitions, il est piloté pas-à-pas). Compile dans blocks_to_runtime3.py.
|
||
def("zacus_onButton", function () {
|
||
this.appendDummyInput().appendField("Quand bouton")
|
||
.appendField(new Blockly.FieldTextInput("NEXT"), "button")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onAudioDone", function () {
|
||
this.appendDummyInput().appendField("Quand audio terminé → aller à")
|
||
.appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onUnlock", function () {
|
||
this.appendDummyInput().appendField("Quand déverrouillage")
|
||
.appendField(new Blockly.FieldTextInput("door"), "source")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onSerial", function () {
|
||
this.appendDummyInput().appendField("Quand série reçoit")
|
||
.appendField(new Blockly.FieldTextInput("OK"), "token")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onEspnow", function () {
|
||
this.appendDummyInput().appendField("Quand ESP-NOW reçoit")
|
||
.appendField(new Blockly.FieldTextInput("ready"), "command")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onPuzzleSolved", function () {
|
||
this.appendDummyInput().appendField("Quand énigme résolue")
|
||
.appendField(new Blockly.FieldTextInput("1"), "puzzle")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
def("zacus_onCodeAssembled", function () {
|
||
this.appendDummyInput().appendField("Quand code assemblé")
|
||
.appendField(new Blockly.FieldTextInput("4242"), "code")
|
||
.appendField("→ aller à").appendField(new Blockly.FieldTextInput("next_scene"), "target");
|
||
stack(this, ZACUS_COLOR.scene); this.setInputsInline(true);
|
||
});
|
||
|
||
// Logique
|
||
def("zacus_logicIf", function () {
|
||
// condition : reporter branché (appendValueInput "condition") OU champ
|
||
// texte de repli (rétrocompat strings opaques).
|
||
this.appendValueInput("condition").setCheck(null).appendField("si");
|
||
this.appendDummyInput().appendField("ou texte").appendField(new Blockly.FieldTextInput("score > 0"), "condition");
|
||
this.appendStatementInput("body").setCheck(null).appendField("alors");
|
||
this.appendStatementInput("else").setCheck(null).appendField("sinon");
|
||
stack(this, ZACUS_COLOR.logic);
|
||
});
|
||
def("zacus_logicTimer", function () {
|
||
this.appendDummyInput().appendField("Attendre")
|
||
.appendField(new Blockly.FieldNumber(5, 0, 600, 0.1), "seconds").appendField("s");
|
||
stack(this, ZACUS_COLOR.logic); this.setInputsInline(true);
|
||
});
|
||
def("zacus_logicScore", function () {
|
||
this.appendDummyInput().appendField("Score +")
|
||
.appendField(new Blockly.FieldNumber(1, -100, 100, 1), "delta");
|
||
stack(this, ZACUS_COLOR.logic); this.setInputsInline(true);
|
||
});
|
||
def("zacus_logicSetVar", function () {
|
||
this.appendDummyInput().appendField("Variable").appendField(new Blockly.FieldTextInput("key"), "name")
|
||
.appendField(":=").appendField(new Blockly.FieldTextInput("42"), "value");
|
||
stack(this, ZACUS_COLOR.logic); this.setInputsInline(true);
|
||
});
|
||
|
||
// ── Vague 1 : Opérateurs (reporters) ──────────────────────────────
|
||
// Tous setOutput(true) : pas de previous/next statement, ils se branchent
|
||
// dans un appendValueInput (condition de logicIf/loopWhile/sceneBranch ou
|
||
// un opérande d'un autre opérateur). Sérialisés en string d'expression.
|
||
def("zacus_opCompare", function () {
|
||
this.appendValueInput("A").setCheck(null);
|
||
this.appendDummyInput().appendField(dropdown(ZACUS_COMPARE_OPS), "op");
|
||
this.appendValueInput("B").setCheck(null);
|
||
this.setInputsInline(true); this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator);
|
||
});
|
||
def("zacus_opBool", function () {
|
||
this.appendValueInput("A").setCheck(null);
|
||
this.appendDummyInput().appendField(dropdown(ZACUS_BOOL_OPS), "op");
|
||
this.appendValueInput("B").setCheck(null);
|
||
this.setInputsInline(true); this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator);
|
||
});
|
||
def("zacus_opNot", function () {
|
||
this.appendValueInput("A").setCheck(null).appendField("non");
|
||
this.setInputsInline(true); this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator);
|
||
});
|
||
def("zacus_opArith", function () {
|
||
this.appendValueInput("A").setCheck(null);
|
||
this.appendDummyInput().appendField(dropdown(ZACUS_ARITH_OPS), "op");
|
||
this.appendValueInput("B").setCheck(null);
|
||
this.setInputsInline(true); this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator);
|
||
});
|
||
def("zacus_valVar", function () {
|
||
this.appendDummyInput().appendField("variable").appendField(new Blockly.FieldTextInput("pct"), "name");
|
||
this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator); this.setInputsInline(true);
|
||
});
|
||
def("zacus_valScore", function () {
|
||
this.appendDummyInput().appendField("score courant");
|
||
this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator); this.setInputsInline(true);
|
||
});
|
||
def("zacus_valNumber", function () {
|
||
this.appendDummyInput().appendField(new Blockly.FieldNumber(0), "num");
|
||
this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator); this.setInputsInline(true);
|
||
});
|
||
def("zacus_valText", function () {
|
||
this.appendDummyInput().appendField("“").appendField(new Blockly.FieldTextInput("texte"), "text").appendField("”");
|
||
this.setOutput(true, null); this.setColour(ZACUS_COLOR.operator); this.setInputsInline(true);
|
||
});
|
||
|
||
// ── Vague 1 : Contrôle (statements c-block) ───────────────────────
|
||
def("zacus_loopRepeat", function () {
|
||
this.appendDummyInput().appendField("répéter")
|
||
.appendField(new Blockly.FieldNumber(10, 0, 100000, 1), "times").appendField("fois");
|
||
this.appendStatementInput("body").setCheck(null).appendField("faire");
|
||
stack(this, ZACUS_COLOR.control);
|
||
});
|
||
def("zacus_loopWhile", function () {
|
||
this.appendValueInput("condition").setCheck(null).appendField("tant que");
|
||
this.appendDummyInput().appendField("ou texte").appendField(new Blockly.FieldTextInput("score < 5"), "condition_text");
|
||
this.appendStatementInput("body").setCheck(null).appendField("faire");
|
||
stack(this, ZACUS_COLOR.control);
|
||
});
|
||
|
||
// ── Vague 2 : Maître du jeu (game-master adaptatif) ───────────────
|
||
// Définit une phase de jeu (intro/profiling/adaptive/climax/outro) avec
|
||
// une durée et un déclencheur. phases.*.{id,duration_minutes,trigger}.
|
||
def("zacus_phase", function () {
|
||
this.appendDummyInput().appendField("🎬 Phase").appendField(new Blockly.FieldTextInput("adaptive"), "id");
|
||
this.appendDummyInput().appendField("durée")
|
||
.appendField(new Blockly.FieldNumber(20, 0, 240, 1), "duration_minutes").appendField("min");
|
||
this.appendDummyInput().appendField("déclencheur").appendField(dropdown(ZACUS_PHASE_TRIGGERS), "trigger");
|
||
stack(this, ZACUS_COLOR.gm);
|
||
});
|
||
// Règle condition→action du game-master (npc_mood_rules.*). La condition
|
||
// accepte un reporter branché OU un texte (comme logicIf).
|
||
def("zacus_gmRule", function () {
|
||
this.appendValueInput("condition").setCheck(null).appendField("⚖ Règle si");
|
||
this.appendDummyInput().appendField("ou texte").appendField(new Blockly.FieldTextInput("pct > 130"), "condition");
|
||
this.appendDummyInput().appendField("→ action").appendField(dropdown(ZACUS_GM_ACTIONS), "action");
|
||
stack(this, ZACUS_COLOR.gm);
|
||
});
|
||
// Humeur du NPC (npc_mood_rules.*.mood).
|
||
def("zacus_npcSetMood", function () {
|
||
this.appendDummyInput().appendField("😐 Humeur NPC").appendField(dropdown(ZACUS_NPC_MOODS), "mood");
|
||
stack(this, ZACUS_COLOR.gm); this.setInputsInline(true);
|
||
});
|
||
// Donner un indice de niveau N avec sa clé (hint_keys.level_N + give level).
|
||
def("zacus_giveHint", function () {
|
||
this.appendDummyInput().appendField("💡 Indice niveau").appendField(dropdown([["1","1"],["2","2"],["3","3"]]), "level")
|
||
.appendField("clé").appendField(new Blockly.FieldTextInput("hints.P5_MORSE.level_1"), "hint_key");
|
||
stack(this, ZACUS_COLOR.gm); this.setInputsInline(true);
|
||
});
|
||
// Indice proactif à intervalle régulier (hint_escalation.proactive_hint_interval_seconds).
|
||
def("zacus_proactiveHint", function () {
|
||
this.appendDummyInput().appendField("🔔 Indice proactif toutes les")
|
||
.appendField(new Blockly.FieldNumber(90, 1, 3600, 1), "interval_seconds").appendField("s")
|
||
.appendField("clé").appendField(new Blockly.FieldTextInput("hints.P5_MORSE.level_1"), "hint_key");
|
||
stack(this, ZACUS_COLOR.gm); this.setInputsInline(true);
|
||
});
|
||
// Définit le parcours (liste de puzzles) pour un profil/classe
|
||
// (group_classification.*.parcours).
|
||
def("zacus_setParcours", function () {
|
||
this.appendDummyInput().appendField("🧭 Parcours profil").appendField(new Blockly.FieldTextInput("TECH"), "profile");
|
||
this.appendDummyInput().appendField("puzzles (dans l'ordre, virgules)")
|
||
.appendField(new Blockly.FieldTextInput("P2_CIRCUIT, P4_RADIO, P5_MORSE"), "puzzles");
|
||
stack(this, ZACUS_COLOR.gm);
|
||
});
|
||
// Bonus conditionnel (scoring.bonus_*). condition = reporter branché OU texte.
|
||
def("zacus_scoreBonus", function () {
|
||
this.appendValueInput("condition").setCheck(null).appendField("🏆 Bonus si");
|
||
this.appendDummyInput().appendField("ou texte").appendField(new Blockly.FieldTextInput("hints_used == 0"), "condition");
|
||
this.appendDummyInput().appendField("→ +").appendField(new Blockly.FieldNumber(200, 0, 10000, 1), "points").appendField("pts");
|
||
stack(this, ZACUS_COLOR.gm);
|
||
});
|
||
// Pénalité de score (scoring.time_penalty_per_minute / hint_penalty).
|
||
def("zacus_scorePenalty", function () {
|
||
this.appendDummyInput().appendField("⛔ Pénalité").appendField(dropdown(ZACUS_PENALTY_KINDS), "penalty_kind")
|
||
.appendField("−").appendField(new Blockly.FieldNumber(10, 0, 10000, 1), "points").appendField("pts");
|
||
stack(this, ZACUS_COLOR.gm); this.setInputsInline(true);
|
||
});
|
||
}
|
||
|
||
export const ZACUS_BLOCK_TYPES = [
|
||
"zacus_sceneStart","zacus_sceneEnd","zacus_sceneGoto","zacus_sceneBranch",
|
||
"zacus_onButton","zacus_onAudioDone","zacus_onUnlock","zacus_onSerial","zacus_onEspnow","zacus_onPuzzleSolved","zacus_onCodeAssembled",
|
||
"zacus_npcSay","zacus_npcWaitResponse","zacus_npcIntentMatch",
|
||
"zacus_hwSoundPlay","zacus_hwAudioStop","zacus_hwAudioVolume","zacus_mediaPlay",
|
||
"zacus_hwLCDText","zacus_hwLCDClear","zacus_hwLCDImage","zacus_hwLCDTouchWait",
|
||
"zacus_hwServo","zacus_hwReadQR","zacus_hwLEDPattern","zacus_hwBuzzerTone","zacus_hwRelay","zacus_hwSensorRead","zacus_hwButtonWait","zacus_hwNFCRead",
|
||
"zacus_espnowRegisterPeer","zacus_espnowSend","zacus_espnowBroadcast","zacus_espnowWait",
|
||
"zacus_boxIMUShake","zacus_boxIRSend",
|
||
"zacus_m5Beep","zacus_m5LCDText","zacus_m5ButtonAB","zacus_m5RGBLed","zacus_m5IMUShake",
|
||
"zacus_plipRing","zacus_plipPickupWait",
|
||
"zacus_scene","zacus_puzzleQR","zacus_puzzleSound",
|
||
"zacus_puzzleLED","zacus_puzzleRadio","zacus_puzzleMorse","zacus_puzzleNFC","zacus_puzzleCoffre",
|
||
"zacus_codeAssembly","zacus_codeFragment","zacus_coffreVerify",
|
||
"zacus_hwOnBoard",
|
||
"zacus_logicIf","zacus_logicTimer","zacus_logicScore","zacus_logicSetVar",
|
||
"zacus_opCompare","zacus_opBool","zacus_opNot","zacus_opArith",
|
||
"zacus_valVar","zacus_valScore","zacus_valNumber","zacus_valText",
|
||
"zacus_loopRepeat","zacus_loopWhile",
|
||
"zacus_phase","zacus_gmRule","zacus_npcSetMood","zacus_giveHint","zacus_proactiveHint","zacus_setParcours","zacus_scoreBonus","zacus_scorePenalty",
|
||
];
|
||
|
||
export const ZACUS_FIELDS_BY_KIND = {
|
||
zacus_sceneStart: ["id"],
|
||
zacus_sceneEnd: [],
|
||
zacus_sceneGoto: ["target"],
|
||
zacus_sceneBranch: ["condition","ifTrue","ifFalse"],
|
||
zacus_npcSay: ["text","voice","emotion","rate"],
|
||
zacus_npcWaitResponse: ["timeout_s"],
|
||
zacus_npcIntentMatch: ["intent","then"],
|
||
// Phase D4 — transitions réelles : « Quand … → aller à [target] ».
|
||
zacus_onButton: ["button","target"],
|
||
zacus_onAudioDone: ["target"],
|
||
zacus_onUnlock: ["source","target"],
|
||
zacus_onSerial: ["token","target"],
|
||
zacus_onEspnow: ["command","target"],
|
||
zacus_onPuzzleSolved: ["puzzle","target"],
|
||
zacus_onCodeAssembled: ["code","target"],
|
||
zacus_hwSoundPlay: ["asset"],
|
||
zacus_hwAudioStop: [],
|
||
zacus_hwAudioVolume: ["level"],
|
||
// Phase D3 — média réel SD : chemin SD + boucle. board hérité de hwOnBoard.
|
||
zacus_mediaPlay: ["path","loop"],
|
||
zacus_hwLCDText: ["line","text"],
|
||
zacus_hwLCDClear: [],
|
||
zacus_hwLCDImage: ["asset"],
|
||
zacus_hwLCDTouchWait: ["zone","timeout_s"],
|
||
zacus_hwServo: ["channel","angle"],
|
||
zacus_hwReadQR: ["expected"],
|
||
zacus_hwLEDPattern: ["pattern"],
|
||
zacus_hwBuzzerTone: ["freq","ms"],
|
||
zacus_hwRelay: ["channel","state"],
|
||
zacus_hwSensorRead: ["pin","var"],
|
||
zacus_hwButtonWait: ["button","timeout_s"],
|
||
zacus_hwNFCRead: ["expected","var","timeout_s"],
|
||
zacus_espnowRegisterPeer: ["alias","mac"],
|
||
zacus_espnowSend: ["peer","command"],
|
||
zacus_espnowBroadcast: ["command"],
|
||
zacus_espnowWait: ["command","timeout_s"],
|
||
zacus_boxIMUShake: ["threshold","timeout_s"],
|
||
zacus_boxIRSend: ["protocol","code"],
|
||
zacus_m5Beep: ["freq","ms"],
|
||
zacus_m5LCDText: ["text","color","size"],
|
||
zacus_m5ButtonAB: ["button","timeout_s"],
|
||
zacus_m5RGBLed: ["color"],
|
||
zacus_m5IMUShake: ["threshold","timeout_s"],
|
||
zacus_plipRing: ["duration_s"],
|
||
zacus_plipPickupWait: ["timeout_s"],
|
||
zacus_scene: ["title","subtitle","symbol","effect"],
|
||
zacus_puzzleQR: ["id","codes","fragment","board"],
|
||
zacus_puzzleSound: ["id","melody","tolerance","fragment","board"],
|
||
// Phase D2 — binding carte : bloc contexte (slot `body`, champ `board`).
|
||
zacus_hwOnBoard: ["board"],
|
||
// Vague 3 — énigmes manquantes (step-extra `puzzle`).
|
||
zacus_puzzleLED: ["id","pattern","fragment","board"],
|
||
zacus_puzzleRadio: ["id","frequency","tolerance","fragment","board"],
|
||
zacus_puzzleMorse: ["id","message","mode","fragment","board"],
|
||
zacus_puzzleNFC: ["id","tags","ordered","fragment","board"],
|
||
zacus_puzzleCoffre: ["id","length","fragment","board"],
|
||
// Vague 4 — assemblage du code final (actions).
|
||
zacus_codeAssembly: ["length","fragments"],
|
||
zacus_codeFragment: ["puzzle","positions"],
|
||
zacus_coffreVerify: ["id","on_success"],
|
||
zacus_logicIf: ["condition"],
|
||
zacus_logicTimer: ["seconds"],
|
||
zacus_logicScore: ["delta"],
|
||
zacus_logicSetVar: ["name","value"],
|
||
// Vague 1 — opérateurs (reporters). Les opérandes viennent des value
|
||
// inputs (sérialisés via `inputs`), seuls le dropdown/littéral sont des
|
||
// champs.
|
||
zacus_opCompare: ["op"],
|
||
zacus_opBool: ["op"],
|
||
zacus_opNot: [],
|
||
zacus_opArith: ["op"],
|
||
zacus_valVar: ["name"],
|
||
zacus_valScore: [],
|
||
zacus_valNumber: ["num"],
|
||
zacus_valText: ["text"],
|
||
// Vague 1 — contrôle.
|
||
zacus_loopRepeat: ["times"],
|
||
zacus_loopWhile: ["condition_text"],
|
||
// Vague 2 — Maître du jeu. Les conditions des règles/bonus sont des value
|
||
// inputs (sérialisées via `inputs`) avec repli texte `condition`.
|
||
zacus_phase: ["id","duration_minutes","trigger"],
|
||
zacus_gmRule: ["condition","action"],
|
||
zacus_npcSetMood: ["mood"],
|
||
zacus_giveHint: ["level","hint_key"],
|
||
zacus_proactiveHint: ["interval_seconds","hint_key"],
|
||
zacus_setParcours: ["profile","puzzles"],
|
||
zacus_scoreBonus: ["condition","points"],
|
||
zacus_scorePenalty: ["penalty_kind","points"],
|
||
};
|
||
|
||
// ── DSL honnête : voies « réel » vs « hardware futur » (phase D1) ───────
|
||
// Un bloc est RÉEL s'il est effectivement exécuté — soit on-device (firmware
|
||
// Freenove master : scene/QR/sound, média, relais ESP-NOW), soit côté gateway
|
||
// (logique de transitions/score/variables, couche game-master, opérateurs).
|
||
// Un bloc HARDWARE FUTUR est validé et compilé en IR comme avant, mais AUCUN
|
||
// executor ne le joue (servo, relais, buzzer, LCD tactile, NFC, énigmes
|
||
// LED/Radio/Morse/NFC/Coffre, assemblage code/coffre, cartes M5/BOX-3/PLIP).
|
||
// La compilation (blocks_to_runtime3.py) émet un *warning* non bloquant pour
|
||
// ces blocs. Garde cette liste en miroir de FUTURE_HW_KINDS côté Python (kinds
|
||
// sans le préfixe `zacus_`).
|
||
export const ZACUS_REAL_KINDS = new Set([
|
||
"zacus_sceneStart","zacus_sceneEnd","zacus_sceneGoto","zacus_sceneBranch",
|
||
"zacus_scene","zacus_puzzleQR","zacus_puzzleSound",
|
||
"zacus_hwOnBoard",
|
||
"zacus_npcSay","zacus_npcWaitResponse","zacus_npcIntentMatch",
|
||
"zacus_onButton","zacus_onAudioDone","zacus_onUnlock","zacus_onSerial","zacus_onEspnow","zacus_onPuzzleSolved","zacus_onCodeAssembled",
|
||
"zacus_hwSoundPlay","zacus_hwAudioStop","zacus_hwAudioVolume","zacus_mediaPlay",
|
||
"zacus_espnowRegisterPeer","zacus_espnowSend","zacus_espnowBroadcast","zacus_espnowWait",
|
||
"zacus_logicIf","zacus_logicTimer","zacus_logicScore","zacus_logicSetVar",
|
||
"zacus_opCompare","zacus_opBool","zacus_opNot","zacus_opArith",
|
||
"zacus_valVar","zacus_valScore","zacus_valNumber","zacus_valText",
|
||
"zacus_loopRepeat","zacus_loopWhile",
|
||
"zacus_phase","zacus_gmRule","zacus_npcSetMood","zacus_giveHint","zacus_proactiveHint",
|
||
"zacus_setParcours","zacus_scoreBonus","zacus_scorePenalty",
|
||
]);
|
||
|
||
export const ZACUS_FUTURE_HW_KINDS = new Set([
|
||
"zacus_hwLCDText","zacus_hwLCDClear","zacus_hwLCDImage","zacus_hwLCDTouchWait",
|
||
"zacus_hwServo","zacus_hwReadQR","zacus_hwLEDPattern","zacus_hwBuzzerTone",
|
||
"zacus_hwRelay","zacus_hwSensorRead","zacus_hwButtonWait","zacus_hwNFCRead",
|
||
"zacus_puzzleLED","zacus_puzzleRadio","zacus_puzzleMorse","zacus_puzzleNFC","zacus_puzzleCoffre",
|
||
"zacus_codeAssembly","zacus_codeFragment","zacus_coffreVerify",
|
||
"zacus_boxIMUShake","zacus_boxIRSend",
|
||
"zacus_m5Beep","zacus_m5LCDText","zacus_m5ButtonAB","zacus_m5RGBLed","zacus_m5IMUShake",
|
||
"zacus_plipRing","zacus_plipPickupWait",
|
||
]);
|
||
|
||
// Toolbox réorganisée en deux voies : catégories RÉELLES en haut, catégories
|
||
// « 🔬 … (futur) » regroupées en bas. Chaque type apparaît exactement une fois ;
|
||
// l'union des deux voies == ZACUS_BLOCK_TYPES.
|
||
export const ZACUS_TOOLBOX_CATEGORIES = [
|
||
// ── Voie réelle (exécutée on-device ou côté gateway) ──────────────────
|
||
{ name: "Cartes", colour: ZACUS_COLOR.hardware, types: ["zacus_hwOnBoard"] },
|
||
{ name: "Scènes", colour: ZACUS_COLOR.scene, types: ["zacus_sceneStart","zacus_sceneEnd","zacus_sceneGoto","zacus_sceneBranch"] },
|
||
{ name: "Transitions", colour: ZACUS_COLOR.scene, types: ["zacus_onButton","zacus_onAudioDone","zacus_onUnlock","zacus_onSerial","zacus_onEspnow","zacus_onPuzzleSolved","zacus_onCodeAssembled"] },
|
||
{ name: "NPC voix", colour: ZACUS_COLOR.npc, types: ["zacus_npcSay","zacus_npcWaitResponse","zacus_npcIntentMatch"] },
|
||
{ name: "Audio", colour: ZACUS_COLOR.audio, types: ["zacus_mediaPlay","zacus_hwSoundPlay","zacus_hwAudioStop","zacus_hwAudioVolume"] },
|
||
{ name: "ESP-NOW", colour: ZACUS_COLOR.espnow, types: ["zacus_espnowRegisterPeer","zacus_espnowSend","zacus_espnowBroadcast","zacus_espnowWait"] },
|
||
{ name: "Énigmes", colour: ZACUS_COLOR.puzzle, types: ["zacus_scene","zacus_puzzleQR","zacus_puzzleSound"] },
|
||
{ name: "Logique", colour: ZACUS_COLOR.logic, types: ["zacus_logicIf","zacus_logicTimer","zacus_logicScore","zacus_logicSetVar"] },
|
||
{ name: "Opérateurs", colour: ZACUS_COLOR.operator, types: ["zacus_opCompare","zacus_opBool","zacus_opNot","zacus_opArith","zacus_valVar","zacus_valScore","zacus_valNumber","zacus_valText"] },
|
||
{ name: "Contrôle", colour: ZACUS_COLOR.control, types: ["zacus_loopRepeat","zacus_loopWhile"] },
|
||
{ name: "Maître du jeu", colour: ZACUS_COLOR.gm, types: ["zacus_phase","zacus_gmRule","zacus_npcSetMood","zacus_giveHint","zacus_proactiveHint","zacus_setParcours","zacus_scoreBonus","zacus_scorePenalty"] },
|
||
// ── Voie hardware futur (validée, non exécutée on-device) ─────────────
|
||
{ name: "🔬 Énigmes (futur)", colour: ZACUS_COLOR.puzzle, types: ["zacus_puzzleLED","zacus_puzzleRadio","zacus_puzzleMorse","zacus_puzzleNFC","zacus_puzzleCoffre"] },
|
||
{ name: "🔬 Coffre (futur)", colour: ZACUS_COLOR.puzzle, types: ["zacus_codeAssembly","zacus_codeFragment","zacus_coffreVerify"] },
|
||
{ name: "🔬 LCD (futur)", colour: ZACUS_COLOR.lcd, types: ["zacus_hwLCDText","zacus_hwLCDClear","zacus_hwLCDImage","zacus_hwLCDTouchWait"] },
|
||
{ name: "🔬 Matériel (futur)", colour: ZACUS_COLOR.hardware, types: ["zacus_hwServo","zacus_hwReadQR","zacus_hwLEDPattern","zacus_hwBuzzerTone","zacus_hwRelay","zacus_hwSensorRead","zacus_hwButtonWait","zacus_hwNFCRead"] },
|
||
{ name: "🔬 BOX-3 (futur)", colour: ZACUS_COLOR.box3, types: ["zacus_boxIMUShake","zacus_boxIRSend"] },
|
||
{ name: "🔬 M5 (futur)", colour: ZACUS_COLOR.m5, types: ["zacus_m5Beep","zacus_m5LCDText","zacus_m5ButtonAB","zacus_m5RGBLed","zacus_m5IMUShake"] },
|
||
{ name: "🔬 PLIP (futur)", colour: ZACUS_COLOR.plip, types: ["zacus_plipRing","zacus_plipPickupWait"] },
|
||
];
|