feat(blockly): onCodeAssembled transition block (completes D4 puzzle-state branching)
This commit is contained in:
@@ -520,6 +520,12 @@ function registerZacusBlocks(Blockly) {
|
||||
.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 () {
|
||||
@@ -665,7 +671,7 @@ function registerZacusBlocks(Blockly) {
|
||||
|
||||
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_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",
|
||||
@@ -700,6 +706,7 @@ const ZACUS_FIELDS_BY_KIND = {
|
||||
zacus_onSerial: ["token","target"],
|
||||
zacus_onEspnow: ["command","target"],
|
||||
zacus_onPuzzleSolved: ["puzzle","target"],
|
||||
zacus_onCodeAssembled: ["code","target"],
|
||||
zacus_hwSoundPlay: ["asset"],
|
||||
zacus_hwAudioStop: [],
|
||||
zacus_hwAudioVolume: ["level"],
|
||||
@@ -790,7 +797,7 @@ const ZACUS_REAL_KINDS = new Set([
|
||||
"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_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",
|
||||
@@ -819,7 +826,7 @@ 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"] },
|
||||
{ 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"] },
|
||||
|
||||
@@ -516,6 +516,12 @@ export function registerZacusBlocks(Blockly) {
|
||||
.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 () {
|
||||
@@ -661,7 +667,7 @@ export function registerZacusBlocks(Blockly) {
|
||||
|
||||
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_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",
|
||||
@@ -696,6 +702,7 @@ export const ZACUS_FIELDS_BY_KIND = {
|
||||
zacus_onSerial: ["token","target"],
|
||||
zacus_onEspnow: ["command","target"],
|
||||
zacus_onPuzzleSolved: ["puzzle","target"],
|
||||
zacus_onCodeAssembled: ["code","target"],
|
||||
zacus_hwSoundPlay: ["asset"],
|
||||
zacus_hwAudioStop: [],
|
||||
zacus_hwAudioVolume: ["level"],
|
||||
@@ -786,7 +793,7 @@ export const ZACUS_REAL_KINDS = new Set([
|
||||
"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_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",
|
||||
@@ -815,7 +822,7 @@ 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"] },
|
||||
{ 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"] },
|
||||
|
||||
@@ -9,6 +9,7 @@ Mapping (blocks_to_runtime3.py):
|
||||
onSerial -> {event_type: serial, event_name: <token>}
|
||||
onEspnow -> {event_type: espnow, event_name: <command>}
|
||||
onPuzzleSolved -> {event_type: unlock, event_name: "puzzle:<id>"}
|
||||
onCodeAssembled-> {event_type: unlock, event_name: "code:<code>"}
|
||||
|
||||
Each carries target_step_id = STEP_<target> (same construction as sceneGoto).
|
||||
EVENT_TYPES is unchanged by D4 — these event_types are all already valid, the
|
||||
@@ -133,6 +134,18 @@ class TransitionBlockExport(unittest.TestCase):
|
||||
self.assertEqual(t["target_step_id"], "STEP_fin")
|
||||
validate_runtime3_document(ir)
|
||||
|
||||
def test_on_code_assembled_models_as_named_unlock(self):
|
||||
ir = _compile(_chain(
|
||||
START,
|
||||
{"kind": "onCodeAssembled", "params": {"code": "4242", "target": "fin"}},
|
||||
TARGET,
|
||||
))
|
||||
t = _only_transition(ir)
|
||||
self.assertEqual(t["event_type"], "unlock")
|
||||
self.assertEqual(t["event_name"], "code:4242")
|
||||
self.assertEqual(t["target_step_id"], "STEP_fin")
|
||||
validate_runtime3_document(ir)
|
||||
|
||||
def test_all_event_types_are_valid(self):
|
||||
for kind, params in (
|
||||
("onButton", {"button": "A", "target": "fin"}),
|
||||
@@ -141,6 +154,7 @@ class TransitionBlockExport(unittest.TestCase):
|
||||
("onSerial", {"token": "x", "target": "fin"}),
|
||||
("onEspnow", {"command": "x", "target": "fin"}),
|
||||
("onPuzzleSolved", {"puzzle": "2", "target": "fin"}),
|
||||
("onCodeAssembled", {"code": "1337", "target": "fin"}),
|
||||
):
|
||||
ir = _compile(_chain(START, {"kind": kind, "params": params}, TARGET))
|
||||
t = _only_transition(ir)
|
||||
|
||||
@@ -25,6 +25,9 @@ Block-to-IR mapping
|
||||
- `onPuzzleSolved`→ `{event_type: unlock, event_name: "puzzle:<id>"}`
|
||||
(le « puzzle résolu » se modélise comme un unlock nommé,
|
||||
EVENT_TYPES n'ayant pas d'event puzzle_state dédié).
|
||||
- `onCodeAssembled`→ `{event_type: unlock, event_name: "code:<code>"}`
|
||||
(le « code assemblé » se modélise comme un unlock nommé,
|
||||
même famille que onPuzzleSolved — complète le set D4).
|
||||
- `hwServo` → action `{kind: hw_servo, channel, angle}`.
|
||||
- `hwReadQR` → action `{kind: qr_expect, value}` (+ serial transition).
|
||||
- `hwLEDPattern` → action `{kind: led_pattern, pattern}`.
|
||||
@@ -649,7 +652,7 @@ def _node_to_action_or_transition(
|
||||
# stricte des ids vit côté runtime3_common._normalize_transition pour le
|
||||
# chemin firmware-import ; le chemin blocks_studio émet STEP_<target> brut,
|
||||
# cohérent avec les transitions existantes). target vide → bloc ignoré.
|
||||
if kind in ("onButton", "onAudioDone", "onUnlock", "onSerial", "onEspnow", "onPuzzleSolved"):
|
||||
if kind in ("onButton", "onAudioDone", "onUnlock", "onSerial", "onEspnow", "onPuzzleSolved", "onCodeAssembled"):
|
||||
target = str(params.get("target", "") or "").strip()
|
||||
if not target:
|
||||
warnings.append(f"{kind} without 'target' ignored")
|
||||
@@ -690,8 +693,9 @@ def _node_to_action_or_transition(
|
||||
"target_step_id": target_step_id,
|
||||
"priority": 5,
|
||||
})
|
||||
# onPuzzleSolved : « énigme résolue » se modélise comme un unlock nommé
|
||||
# (event_type unlock, event_name puzzle:<id>) — le firmware n'expose pas
|
||||
if kind == "onPuzzleSolved":
|
||||
# « énigme résolue » se modélise comme un unlock nommé (event_type
|
||||
# unlock, event_name puzzle:<id>) — le firmware n'expose pas
|
||||
# d'event_type puzzle_state dédié, et EVENT_TYPES ne s'étend pas en D4.
|
||||
return ("transitions", {
|
||||
"event_type": "unlock",
|
||||
@@ -699,6 +703,15 @@ def _node_to_action_or_transition(
|
||||
"target_step_id": target_step_id,
|
||||
"priority": 5,
|
||||
})
|
||||
# onCodeAssembled : « code assemblé » se modélise comme un unlock nommé
|
||||
# (event_type unlock, event_name code:<code>) — même famille que
|
||||
# onPuzzleSolved, complète le branchement D4 sur l'état des énigmes.
|
||||
return ("transitions", {
|
||||
"event_type": "unlock",
|
||||
"event_name": f"code:{str(params.get('code', '') or '').strip()}",
|
||||
"target_step_id": target_step_id,
|
||||
"priority": 5,
|
||||
})
|
||||
if kind == "sceneGoto":
|
||||
target = params.get("target", "").strip()
|
||||
return ("transitions", {
|
||||
@@ -900,7 +913,7 @@ def _walk_slot_actions(parent: dict, slot_name: str, nodes_by_id: dict[str, dict
|
||||
kind = node.get("kind", "")
|
||||
_warn_future_hw(kind, warnings, future_hw_seen)
|
||||
if kind in ("sceneStart", "sceneEnd", "sceneGoto", "sceneBranch", "npcIntentMatch", "logicTimer",
|
||||
"onButton", "onAudioDone", "onUnlock", "onSerial", "onEspnow", "onPuzzleSolved",
|
||||
"onButton", "onAudioDone", "onUnlock", "onSerial", "onEspnow", "onPuzzleSolved", "onCodeAssembled",
|
||||
"scene", "puzzleQR", "puzzleSound",
|
||||
"puzzleLED", "puzzleRadio", "puzzleMorse", "puzzleNFC", "puzzleCoffre",
|
||||
"phase", "setParcours", "scorePenalty"):
|
||||
|
||||
Reference in New Issue
Block a user