feat: Clean up unused screens and improve storage management
- 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.
This commit is contained in:
@@ -134,26 +134,6 @@ prompt_input:
|
||||
screen_json: "data/story/screens/SCENE_LOCKED.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WIN_ETAPE"
|
||||
screen_json: "data/story/screens/SCENE_WIN_ETAPE.json"
|
||||
default_audio_pack_id: "PACK_WIN"
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WINNER"
|
||||
screen_json: "data/story/screens/SCENE_WINNER.json"
|
||||
default_audio_pack_id: "PACK_WIN"
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_FIREWORKS"
|
||||
screen_json: "data/story/screens/SCENE_FIREWORKS.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_BROKEN"
|
||||
screen_json: "data/story/screens/SCENE_BROKEN.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WIN"
|
||||
screen_json: "data/story/screens/SCENE_WIN.json"
|
||||
default_audio_pack_id: "PACK_WIN"
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_REWARD"
|
||||
screen_json: "data/story/screens/SCENE_REWARD.json"
|
||||
default_audio_pack_id: ""
|
||||
@@ -162,22 +142,6 @@ prompt_input:
|
||||
screen_json: "data/story/screens/SCENE_SEARCH.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_SIGNAL_SPIKE"
|
||||
screen_json: "data/story/screens/SCENE_SIGNAL_SPIKE.json"
|
||||
default_audio_pack_id: "PACK_MORSE_HINT"
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_MEDIA_ARCHIVE"
|
||||
screen_json: "data/story/screens/SCENE_MEDIA_ARCHIVE.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_CAMERA_SCAN"
|
||||
screen_json: "data/story/screens/SCENE_CAMERA_SCAN.json"
|
||||
default_audio_pack_id: ""
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_LA_DETECT"
|
||||
screen_json: "data/story/screens/SCENE_LA_DETECT.json"
|
||||
default_audio_pack_id: "PACK_SONAR_HINT"
|
||||
runtime_step_ids: []
|
||||
|
||||
audio_pack_catalog_all:
|
||||
- pack_id: "PACK_BOOT_RADIO"
|
||||
@@ -274,31 +238,6 @@ prompt_input:
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_LOCKED", "scenario_reset"]
|
||||
default_exit_triggers: ["UNLOCK", "NEXT", "SC_EVENT button ANY"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WIN_ETAPE"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_WIN_ETAPE"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_LOCKED"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WINNER"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_WINNER"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_MEDIA_MANAGER"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_FIREWORKS"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_FIREWORKS"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_MEDIA_MANAGER"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_BROKEN"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_BROKEN"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_LOCKED"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_WIN"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_WIN"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_REWARD"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_REWARD"]
|
||||
@@ -309,26 +248,6 @@ prompt_input:
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_SEARCH"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_SIGNAL_SPIKE"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_SIGNAL_SPIKE"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_MEDIA_ARCHIVE"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_MEDIA_ARCHIVE"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_MEDIA_MANAGER", "SCENE_GOTO SCENE_READY"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_CAMERA_SCAN"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_CAMERA_SCAN"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_MEDIA_MANAGER"]
|
||||
runtime_step_ids: []
|
||||
- scene_id: "SCENE_LA_DETECT"
|
||||
default_actions: []
|
||||
default_entry_triggers: ["SCENE_GOTO SCENE_LA_DETECT"]
|
||||
default_exit_triggers: ["SCENE_GOTO SCENE_READY", "SCENE_GOTO SCENE_LA_DETECTOR"]
|
||||
runtime_step_ids: []
|
||||
|
||||
scenario:
|
||||
id: "DEFAULT"
|
||||
|
||||
@@ -1,35 +1,17 @@
|
||||
meta:
|
||||
purpose: Edit LVGL/FX/audio mapping scene by scene, then sync to screen JSON.
|
||||
source_runtime_scenario: game/scenarios/default_unlock_win_etape2.yaml
|
||||
review:
|
||||
status: coherent
|
||||
scope: runtime_scenes_only
|
||||
checks:
|
||||
- screen_json_exists
|
||||
- runtime_step_ids_match_default_scenario
|
||||
- effect_tokens_supported_by_ui_manager
|
||||
- transition_tokens_supported_by_ui_manager
|
||||
- unused_scenes_pruned
|
||||
scope: runtime_only
|
||||
scene_order_source: game/scenarios/default_unlock_win_etape2.yaml#steps
|
||||
used_scene_count: 9
|
||||
removed_unused_scene_count: 15
|
||||
removed_unused_scene_ids:
|
||||
- SCENE_BROKEN
|
||||
- SCENE_CAMERA_SCAN
|
||||
- SCENE_FIREWORKS
|
||||
- SCENE_LA_DETECT
|
||||
exported_scene_count: 9
|
||||
unused_scene_ids:
|
||||
- SCENE_LOCKED
|
||||
- SCENE_MEDIA_ARCHIVE
|
||||
- SCENE_MP3_PLAYER
|
||||
- SCENE_PHOTO_MANAGER
|
||||
- SCENE_READY
|
||||
- SCENE_REWARD
|
||||
- SCENE_SEARCH
|
||||
- SCENE_SIGNAL_SPIKE
|
||||
- SCENE_WIN
|
||||
- SCENE_WINNER
|
||||
- SCENE_WIN_ETAPE
|
||||
missing_screen_ids: []
|
||||
scenes:
|
||||
- scene_id: SCENE_U_SON_PROTO
|
||||
screen_json: hardware/firmware/data/story/screens/SCENE_U_SON_PROTO.json
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"id": "DEFAULT",
|
||||
"scenario": "DEFAULT",
|
||||
"initial_step": "SCENE_U_SON_PROTO",
|
||||
"steps": [
|
||||
{
|
||||
"id": "SCENE_U_SON_PROTO",
|
||||
"screen_scene_id": "SCENE_U_SON_PROTO",
|
||||
"audio_pack_id": "PACK_BOOT_RADIO",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_HW_LED_ALERT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SCENE_LA_DETECTOR",
|
||||
"screen_scene_id": "SCENE_LA_DETECTOR",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_QUEUE_SONAR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "RTC_ESP_ETAPE1",
|
||||
"screen_scene_id": "SCENE_WIN_ETAPE1",
|
||||
"audio_pack_id": "PACK_CONFIRM_WIN_ETAPE1",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_HW_LED_ALERT",
|
||||
"ACTION_ESP_NOW_SEND_ETAPE1",
|
||||
"ACTION_QUEUE_SONAR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "WIN_ETAPE1",
|
||||
"screen_scene_id": "SCENE_WIN_ETAPE1",
|
||||
"audio_pack_id": "PACK_WIN",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_HW_LED_ALERT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "STEP_WARNING",
|
||||
"screen_scene_id": "SCENE_WARNING",
|
||||
"audio_pack_id": "PACK_BOOT_RADIO",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_HW_LED_ALERT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SCENE_LEFOU_DETECTOR",
|
||||
"screen_scene_id": "SCENE_LEFOU_DETECTOR",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_QUEUE_SONAR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "RTC_ESP_ETAPE2",
|
||||
"screen_scene_id": "SCENE_WIN_ETAPE2",
|
||||
"audio_pack_id": "PACK_CONFIRM_WIN_ETAPE2",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_HW_LED_ALERT",
|
||||
"ACTION_ESP_NOW_SEND_ETAPE2",
|
||||
"ACTION_QUEUE_SONAR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SCENE_QR_DETECTOR",
|
||||
"screen_scene_id": "SCENE_QR_DETECTOR",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_QR_CODE_SCANNER_START"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SCENE_FINAL_WIN",
|
||||
"screen_scene_id": "SCENE_FINAL_WIN",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_WINNER"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "STEP_MEDIA_MANAGER",
|
||||
"screen_scene_id": "SCENE_MEDIA_MANAGER",
|
||||
"action_ids": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_SET_BOOT_MEDIA_MANAGER"
|
||||
],
|
||||
"mp3_gate_open": true
|
||||
}
|
||||
],
|
||||
"source": "littlefs-default-options"
|
||||
}
|
||||
@@ -1,24 +1,19 @@
|
||||
{
|
||||
"id": "SCENE_LA_DETECTOR",
|
||||
"title": "DETECTEUR DE RESONNANCE",
|
||||
"subtitle": "",
|
||||
"symbol": "AUDIO",
|
||||
"effect": "wave",
|
||||
"title": "LA DETECTOR",
|
||||
"subtitle": "Trouve la bonne resonance",
|
||||
"symbol": "LA",
|
||||
"effect": "radar",
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_subtitle": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 480,
|
||||
"waveform": {
|
||||
"enabled": true,
|
||||
"sample_count": 16,
|
||||
"amplitude_pct": 100,
|
||||
"jitter": true
|
||||
}
|
||||
"effect_speed_ms": 460
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#49D9FF",
|
||||
"text": "#E8F6FF"
|
||||
"bg": "#111B20",
|
||||
"accent": "#86FFD2",
|
||||
"text": "#ECFFF6"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
@@ -67,7 +62,7 @@
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "zoom",
|
||||
"duration_ms": 260
|
||||
"effect": "fade",
|
||||
"duration_ms": 180
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_symbol": false,
|
||||
"effect_speed_ms": 90
|
||||
"effect_speed_ms": 90,
|
||||
"waveform": {
|
||||
"enabled": true,
|
||||
"sample_count": 16,
|
||||
"amplitude_pct": 100,
|
||||
"jitter": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#06060E",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,41 +1 @@
|
||||
{
|
||||
"id": "EXAMPLE_UNLOCK_EXPRESS",
|
||||
"scenario": "EXAMPLE_UNLOCK_EXPRESS",
|
||||
"version": 2,
|
||||
"initial_step": "STEP_WAIT_UNLOCK",
|
||||
"hardware_events": {
|
||||
"button_short_1": "UNLOCK",
|
||||
"button_short_5": "BTN_NEXT",
|
||||
"button_long_4": "FORCE_DONE"
|
||||
},
|
||||
"app_bindings": [
|
||||
"APP_LA",
|
||||
"APP_SCREEN",
|
||||
"APP_GATE",
|
||||
"APP_AUDIO",
|
||||
"APP_WIFI",
|
||||
"APP_ESPNOW"
|
||||
],
|
||||
"actions_catalog": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_REFRESH_SD"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "STEP_WAIT_UNLOCK",
|
||||
"screen_scene_id": "SCENE_LOCKED"
|
||||
},
|
||||
{
|
||||
"id": "STEP_WIN",
|
||||
"screen_scene_id": "SCENE_REWARD",
|
||||
"audio_pack_id": "PACK_WIN"
|
||||
},
|
||||
{
|
||||
"id": "STEP_DONE",
|
||||
"screen_scene_id": "SCENE_READY"
|
||||
}
|
||||
],
|
||||
"source": "story_selector",
|
||||
"screen_root": "/story/screens",
|
||||
"audio_root": "/story/audio"
|
||||
}
|
||||
{"app_bindings":[{"app":"LA_DETECTOR","config":{"hold_ms":3000,"require_listening":true,"unlock_event":"UNLOCK"},"id":"APP_LA"},{"app":"AUDIO_PACK","config":null,"id":"APP_AUDIO"},{"app":"SCREEN_SCENE","config":null,"id":"APP_SCREEN"},{"app":"MP3_GATE","config":null,"id":"APP_GATE"},{"app":"WIFI_STACK","config":null,"id":"APP_WIFI"},{"app":"ESPNOW_STACK","config":null,"id":"APP_ESPNOW"}],"estimated_duration_s":0,"id":"EXAMPLE_UNLOCK_EXPRESS","initial_step":"STEP_WAIT_UNLOCK","steps":[{"actions":["ACTION_TRACE_STEP"],"apps":["APP_LA","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":false,"screen_scene_id":"SCENE_LOCKED","step_id":"STEP_WAIT_UNLOCK","transitions":[{"after_ms":0,"event_name":"UNLOCK","event_type":"unlock","id":"TR_STEP_WAIT_UNLOCK_1","priority":100,"target_step_id":"STEP_WIN","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_WIN","mp3_gate_open":false,"screen_scene_id":"SCENE_REWARD","step_id":"STEP_WIN","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_WIN_1","priority":100,"target_step_id":"STEP_DONE","trigger":"on_event"},{"after_ms":0,"event_name":"FORCE_DONE","event_type":"serial","id":"TR_STEP_WIN_2","priority":120,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP","ACTION_REFRESH_SD"],"apps":["APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":true,"screen_scene_id":"SCENE_READY","step_id":"STEP_DONE","transitions":[]}],"version":2,"scenario":"EXAMPLE_UNLOCK_EXPRESS"}
|
||||
|
||||
@@ -1,41 +1 @@
|
||||
{
|
||||
"id": "EXEMPLE_UNLOCK_EXPRESS_DONE",
|
||||
"scenario": "EXEMPLE_UNLOCK_EXPRESS_DONE",
|
||||
"version": 2,
|
||||
"initial_step": "STEP_WAIT_UNLOCK",
|
||||
"hardware_events": {
|
||||
"button_short_1": "UNLOCK",
|
||||
"button_short_5": "BTN_NEXT",
|
||||
"button_long_4": "FORCE_DONE"
|
||||
},
|
||||
"app_bindings": [
|
||||
"APP_LA",
|
||||
"APP_SCREEN",
|
||||
"APP_GATE",
|
||||
"APP_AUDIO",
|
||||
"APP_WIFI",
|
||||
"APP_ESPNOW"
|
||||
],
|
||||
"actions_catalog": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_REFRESH_SD"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "STEP_WAIT_UNLOCK",
|
||||
"screen_scene_id": "SCENE_LOCKED"
|
||||
},
|
||||
{
|
||||
"id": "STEP_WIN",
|
||||
"screen_scene_id": "SCENE_REWARD",
|
||||
"audio_pack_id": "PACK_WIN"
|
||||
},
|
||||
{
|
||||
"id": "STEP_DONE",
|
||||
"screen_scene_id": "SCENE_READY"
|
||||
}
|
||||
],
|
||||
"source": "story_selector",
|
||||
"screen_root": "/story/screens",
|
||||
"audio_root": "/story/audio"
|
||||
}
|
||||
{"app_bindings":[{"app":"LA_DETECTOR","config":{"hold_ms":3000,"require_listening":true,"unlock_event":"UNLOCK"},"id":"APP_LA"},{"app":"AUDIO_PACK","config":null,"id":"APP_AUDIO"},{"app":"SCREEN_SCENE","config":null,"id":"APP_SCREEN"},{"app":"MP3_GATE","config":null,"id":"APP_GATE"},{"app":"WIFI_STACK","config":null,"id":"APP_WIFI"},{"app":"ESPNOW_STACK","config":null,"id":"APP_ESPNOW"}],"estimated_duration_s":0,"id":"EXEMPLE_UNLOCK_EXPRESS_DONE","initial_step":"STEP_WAIT_UNLOCK","steps":[{"actions":["ACTION_TRACE_STEP"],"apps":["APP_LA","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":false,"screen_scene_id":"SCENE_LOCKED","step_id":"STEP_WAIT_UNLOCK","transitions":[{"after_ms":0,"event_name":"UNLOCK","event_type":"unlock","id":"TR_STEP_WAIT_UNLOCK_1","priority":100,"target_step_id":"STEP_WIN","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_WIN","mp3_gate_open":false,"screen_scene_id":"SCENE_REWARD","step_id":"STEP_WIN","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_WIN_1","priority":100,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP","ACTION_REFRESH_SD"],"apps":["APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":true,"screen_scene_id":"SCENE_READY","step_id":"STEP_DONE","transitions":[]}],"version":2,"scenario":"EXEMPLE_UNLOCK_EXPRESS_DONE"}
|
||||
|
||||
@@ -1,53 +1 @@
|
||||
{
|
||||
"id": "SPECTRE_RADIO_LAB",
|
||||
"scenario": "SPECTRE_RADIO_LAB",
|
||||
"version": 2,
|
||||
"initial_step": "STEP_WAIT_UNLOCK",
|
||||
"hardware_events": {
|
||||
"button_short_1": "UNLOCK",
|
||||
"button_short_5": "BTN_NEXT",
|
||||
"button_long_4": "FORCE_DONE",
|
||||
"espnow_event": "SERIAL:<payload>"
|
||||
},
|
||||
"app_bindings": [
|
||||
"APP_LA",
|
||||
"APP_AUDIO",
|
||||
"APP_SCREEN",
|
||||
"APP_GATE",
|
||||
"APP_WIFI",
|
||||
"APP_ESPNOW"
|
||||
],
|
||||
"actions_catalog": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_QUEUE_SONAR",
|
||||
"ACTION_REFRESH_SD"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "STEP_WAIT_UNLOCK",
|
||||
"screen_scene_id": "SCENE_LOCKED"
|
||||
},
|
||||
{
|
||||
"id": "STEP_SONAR_SEARCH",
|
||||
"screen_scene_id": "SCENE_SEARCH",
|
||||
"audio_pack_id": "PACK_SONAR_HINT"
|
||||
},
|
||||
{
|
||||
"id": "STEP_MORSE_CLUE",
|
||||
"screen_scene_id": "SCENE_SEARCH",
|
||||
"audio_pack_id": "PACK_MORSE_HINT"
|
||||
},
|
||||
{
|
||||
"id": "STEP_WIN",
|
||||
"screen_scene_id": "SCENE_REWARD",
|
||||
"audio_pack_id": "PACK_WIN"
|
||||
},
|
||||
{
|
||||
"id": "STEP_DONE",
|
||||
"screen_scene_id": "SCENE_READY"
|
||||
}
|
||||
],
|
||||
"source": "story_selector",
|
||||
"screen_root": "/story/screens",
|
||||
"audio_root": "/story/audio"
|
||||
}
|
||||
{"app_bindings":[{"app":"LA_DETECTOR","config":{"hold_ms":3000,"require_listening":true,"unlock_event":"UNLOCK"},"id":"APP_LA"},{"app":"AUDIO_PACK","config":null,"id":"APP_AUDIO"},{"app":"SCREEN_SCENE","config":null,"id":"APP_SCREEN"},{"app":"MP3_GATE","config":null,"id":"APP_GATE"},{"app":"WIFI_STACK","config":null,"id":"APP_WIFI"},{"app":"ESPNOW_STACK","config":null,"id":"APP_ESPNOW"}],"estimated_duration_s":0,"id":"SPECTRE_RADIO_LAB","initial_step":"STEP_WAIT_UNLOCK","steps":[{"actions":["ACTION_TRACE_STEP","ACTION_QUEUE_SONAR"],"apps":["APP_LA","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":false,"screen_scene_id":"SCENE_LOCKED","step_id":"STEP_WAIT_UNLOCK","transitions":[{"after_ms":0,"event_name":"UNLOCK","event_type":"unlock","id":"TR_STEP_WAIT_UNLOCK_1","priority":100,"target_step_id":"STEP_SONAR_SEARCH","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_SONAR_HINT","mp3_gate_open":false,"screen_scene_id":"SCENE_SEARCH","step_id":"STEP_SONAR_SEARCH","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_SONAR_SEARCH_1","priority":100,"target_step_id":"STEP_MORSE_CLUE","trigger":"on_event"},{"after_ms":0,"event_name":"FORCE_DONE","event_type":"serial","id":"TR_STEP_SONAR_SEARCH_2","priority":120,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_MORSE_HINT","mp3_gate_open":false,"screen_scene_id":"SCENE_SEARCH","step_id":"STEP_MORSE_CLUE","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_MORSE_CLUE_1","priority":100,"target_step_id":"STEP_WIN","trigger":"on_event"},{"after_ms":0,"event_name":"FORCE_DONE","event_type":"serial","id":"TR_STEP_MORSE_CLUE_2","priority":120,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_WIN","mp3_gate_open":false,"screen_scene_id":"SCENE_REWARD","step_id":"STEP_WIN","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_WIN_1","priority":100,"target_step_id":"STEP_DONE","trigger":"on_event"},{"after_ms":0,"event_name":"FORCE_DONE","event_type":"serial","id":"TR_STEP_WIN_2","priority":120,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP","ACTION_REFRESH_SD"],"apps":["APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":true,"screen_scene_id":"SCENE_READY","step_id":"STEP_DONE","transitions":[]}],"version":2,"scenario":"SPECTRE_RADIO_LAB"}
|
||||
|
||||
@@ -1,52 +1 @@
|
||||
{
|
||||
"id": "ZACUS_V1_UNLOCK_ETAPE2",
|
||||
"scenario": "ZACUS_V1_UNLOCK_ETAPE2",
|
||||
"version": 2,
|
||||
"initial_step": "STEP_BOOT_WAIT",
|
||||
"hardware_events": {
|
||||
"button_short_1": "UNLOCK",
|
||||
"button_short_5": "BTN_NEXT",
|
||||
"button_long_3": "FORCE_ETAPE2",
|
||||
"button_long_4": "FORCE_DONE",
|
||||
"espnow_event": "SERIAL:<payload>"
|
||||
},
|
||||
"app_bindings": [
|
||||
"APP_LA",
|
||||
"APP_AUDIO",
|
||||
"APP_SCREEN",
|
||||
"APP_GATE",
|
||||
"APP_WIFI",
|
||||
"APP_ESPNOW"
|
||||
],
|
||||
"actions_catalog": [
|
||||
"ACTION_TRACE_STEP",
|
||||
"ACTION_REFRESH_SD"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "STEP_BOOT_WAIT",
|
||||
"screen_scene_id": "SCENE_LOCKED"
|
||||
},
|
||||
{
|
||||
"id": "STEP_BOOT_USON",
|
||||
"screen_scene_id": "SCENE_LOCKED",
|
||||
"audio_pack_id": "PACK_BOOT_RADIO"
|
||||
},
|
||||
{
|
||||
"id": "STEP_LA_DETECT",
|
||||
"screen_scene_id": "SCENE_SEARCH"
|
||||
},
|
||||
{
|
||||
"id": "STEP_WIN",
|
||||
"screen_scene_id": "SCENE_REWARD",
|
||||
"audio_pack_id": "PACK_WIN"
|
||||
},
|
||||
{
|
||||
"id": "STEP_DONE",
|
||||
"screen_scene_id": "SCENE_READY"
|
||||
}
|
||||
],
|
||||
"source": "story_selector",
|
||||
"screen_root": "/story/screens",
|
||||
"audio_root": "/story/audio"
|
||||
}
|
||||
{"app_bindings":[{"app":"LA_DETECTOR","config":{"hold_ms":3000,"require_listening":true,"unlock_event":"UNLOCK"},"id":"APP_LA"},{"app":"AUDIO_PACK","config":null,"id":"APP_AUDIO"},{"app":"SCREEN_SCENE","config":null,"id":"APP_SCREEN"},{"app":"MP3_GATE","config":null,"id":"APP_GATE"},{"app":"WIFI_STACK","config":null,"id":"APP_WIFI"},{"app":"ESPNOW_STACK","config":null,"id":"APP_ESPNOW"}],"estimated_duration_s":0,"id":"ZACUS_V1_UNLOCK_ETAPE2","initial_step":"STEP_BOOT_WAIT","steps":[{"actions":["ACTION_TRACE_STEP"],"apps":["APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":false,"screen_scene_id":"SCENE_LOCKED","step_id":"STEP_BOOT_WAIT","transitions":[{"after_ms":0,"event_name":"BTN_NEXT","event_type":"serial","id":"TR_STEP_BOOT_WAIT_1","priority":100,"target_step_id":"STEP_BOOT_USON","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_BOOT_RADIO","mp3_gate_open":false,"screen_scene_id":"SCENE_LOCKED","step_id":"STEP_BOOT_USON","transitions":[{"after_ms":0,"event_name":"BTN_NEXT","event_type":"serial","id":"TR_STEP_BOOT_USON_1","priority":100,"target_step_id":"STEP_LA_DETECT","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_LA","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":false,"screen_scene_id":"SCENE_SEARCH","step_id":"STEP_LA_DETECT","transitions":[{"after_ms":0,"event_name":"UNLOCK","event_type":"unlock","id":"TR_STEP_LA_DETECT_1","priority":120,"target_step_id":"STEP_WIN","trigger":"on_event"},{"after_ms":30000,"event_name":"LA_TIMEOUT","event_type":"timer","id":"TR_STEP_LA_DETECT_2","priority":80,"target_step_id":"STEP_BOOT_USON","trigger":"after_ms"}]},{"actions":["ACTION_TRACE_STEP"],"apps":["APP_AUDIO","APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"PACK_WIN","mp3_gate_open":false,"screen_scene_id":"SCENE_REWARD","step_id":"STEP_WIN","transitions":[{"after_ms":0,"event_name":"AUDIO_DONE","event_type":"audio_done","id":"TR_STEP_WIN_1","priority":100,"target_step_id":"STEP_DONE","trigger":"on_event"},{"after_ms":0,"event_name":"FORCE_DONE","event_type":"serial","id":"TR_STEP_WIN_2","priority":120,"target_step_id":"STEP_DONE","trigger":"on_event"}]},{"actions":["ACTION_TRACE_STEP","ACTION_REFRESH_SD"],"apps":["APP_SCREEN","APP_GATE","APP_WIFI","APP_ESPNOW"],"audio_pack_id":"","mp3_gate_open":true,"screen_scene_id":"SCENE_READY","step_id":"STEP_DONE","transitions":[]}],"version":2,"scenario":"ZACUS_V1_UNLOCK_ETAPE2"}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_BROKEN",
|
||||
"title": "PROTO U-SON",
|
||||
"subtitle": "Signal brouille",
|
||||
"symbol": "ALERT",
|
||||
"effect": "blink",
|
||||
"visual": {
|
||||
"show_title": false,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 180
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#2A0508",
|
||||
"accent": "#FF4A45",
|
||||
"text": "#FFF1F1"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 900,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "blink",
|
||||
"speed_ms": 180,
|
||||
"theme": {
|
||||
"bg": "#2A0508",
|
||||
"accent": "#FF4A45",
|
||||
"text": "#FFF1F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 900,
|
||||
"effect": "scan",
|
||||
"speed_ms": 520,
|
||||
"theme": {
|
||||
"bg": "#3A0A10",
|
||||
"accent": "#FF7873",
|
||||
"text": "#FFF7F7"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "glitch",
|
||||
"duration_ms": 160
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_CAMERA_SCAN",
|
||||
"title": "ZACUS QR VALIDATION",
|
||||
"subtitle": "Scanne le QR final",
|
||||
"symbol": "QR",
|
||||
"effect": "none",
|
||||
"qr": {
|
||||
"expected": [
|
||||
"ZACUS:MEDIA_MANAGER",
|
||||
"MEDIA_MANAGER_OK"
|
||||
],
|
||||
"prefix": "ZACUS:",
|
||||
"contains": "MEDIA_MANAGER",
|
||||
"caseInsensitive": true
|
||||
},
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_subtitle": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 0
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#102040",
|
||||
"accent": "#5CA3FF",
|
||||
"text": "#F3F7FF"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1400,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "none",
|
||||
"speed_ms": 0,
|
||||
"theme": {
|
||||
"bg": "#102040",
|
||||
"accent": "#5CA3FF",
|
||||
"text": "#F3F7FF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 700,
|
||||
"effect": "pulse",
|
||||
"speed_ms": 520,
|
||||
"theme": {
|
||||
"bg": "#162B52",
|
||||
"accent": "#89BEFF",
|
||||
"text": "#F7FBFF"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "fade",
|
||||
"duration_ms": 180
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_FIREWORKS",
|
||||
"title": "FIREWORKS",
|
||||
"subtitle": "Mode celebration",
|
||||
"symbol": "WIN",
|
||||
"effect": "celebrate",
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 300
|
||||
},
|
||||
"demo": {
|
||||
"mode": "fireworks",
|
||||
"particle_count": 4,
|
||||
"strobe_level": 90
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#120825",
|
||||
"accent": "#FFB65C",
|
||||
"text": "#FFF4E6"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1000,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "celebrate",
|
||||
"speed_ms": 300,
|
||||
"theme": {
|
||||
"bg": "#120825",
|
||||
"accent": "#FFB65C",
|
||||
"text": "#FFF4E6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1000,
|
||||
"effect": "blink",
|
||||
"speed_ms": 220,
|
||||
"theme": {
|
||||
"bg": "#1C0A31",
|
||||
"accent": "#FFD68D",
|
||||
"text": "#FFFDF6"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "glitch",
|
||||
"duration_ms": 180
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_LA_DETECT",
|
||||
"title": "DETECTEUR DE RESONNANCE",
|
||||
"subtitle": "",
|
||||
"symbol": "AUDIO",
|
||||
"effect": "wave",
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 480,
|
||||
"waveform": {
|
||||
"enabled": true,
|
||||
"sample_count": 16,
|
||||
"amplitude_pct": 100,
|
||||
"jitter": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#49D9FF",
|
||||
"text": "#E8F6FF"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 2400,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "wave",
|
||||
"speed_ms": 480,
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#49D9FF",
|
||||
"text": "#E8F6FF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 800,
|
||||
"effect": "radar",
|
||||
"speed_ms": 620,
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#7EE8FF",
|
||||
"text": "#F2FAFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1600,
|
||||
"effect": "wave",
|
||||
"speed_ms": 340,
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#D8FF6B",
|
||||
"text": "#F9FFD8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 2400,
|
||||
"effect": "radar",
|
||||
"speed_ms": 700,
|
||||
"theme": {
|
||||
"bg": "#000000",
|
||||
"accent": "#49D9FF",
|
||||
"text": "#E8F6FF"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "zoom",
|
||||
"duration_ms": 260
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_MEDIA_ARCHIVE",
|
||||
"title": "ARCHIVES MEDIA",
|
||||
"subtitle": "Photos et enregistrements sauvegardes",
|
||||
"symbol": "READY",
|
||||
"effect": "radar",
|
||||
"visual": {
|
||||
"show_title": false,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 760
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#0D1A34",
|
||||
"accent": "#7CB1FF",
|
||||
"text": "#EEF4FF"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 2000,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "radar",
|
||||
"speed_ms": 760,
|
||||
"theme": {
|
||||
"bg": "#0D1A34",
|
||||
"accent": "#7CB1FF",
|
||||
"text": "#EEF4FF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1000,
|
||||
"effect": "pulse",
|
||||
"speed_ms": 620,
|
||||
"theme": {
|
||||
"bg": "#132245",
|
||||
"accent": "#9CC7FF",
|
||||
"text": "#F7FAFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 2000,
|
||||
"effect": "radar",
|
||||
"speed_ms": 760,
|
||||
"theme": {
|
||||
"bg": "#0D1A34",
|
||||
"accent": "#7CB1FF",
|
||||
"text": "#EEF4FF"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "fade",
|
||||
"duration_ms": 240
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_SIGNAL_SPIKE",
|
||||
"title": "PIC DE SIGNAL",
|
||||
"subtitle": "Interference soudaine detectee",
|
||||
"symbol": "ALERT",
|
||||
"effect": "wave",
|
||||
"visual": {
|
||||
"show_title": false,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 260
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#24090C",
|
||||
"accent": "#FF6A52",
|
||||
"text": "#FFF2EB"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1400,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "wave",
|
||||
"speed_ms": 260,
|
||||
"theme": {
|
||||
"bg": "#24090C",
|
||||
"accent": "#FF6A52",
|
||||
"text": "#FFF2EB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 700,
|
||||
"effect": "blink",
|
||||
"speed_ms": 180,
|
||||
"theme": {
|
||||
"bg": "#2F1014",
|
||||
"accent": "#FF8C73",
|
||||
"text": "#FFF8F5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1400,
|
||||
"effect": "wave",
|
||||
"speed_ms": 320,
|
||||
"theme": {
|
||||
"bg": "#24090C",
|
||||
"accent": "#FF6A52",
|
||||
"text": "#FFF2EB"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "glitch",
|
||||
"duration_ms": 170
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_WIN",
|
||||
"title": "VICTOIRE",
|
||||
"subtitle": "Etape validee",
|
||||
"symbol": "WIN",
|
||||
"effect": "celebrate",
|
||||
"visual": {
|
||||
"show_title": false,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 420
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#231038",
|
||||
"accent": "#F4CB4A",
|
||||
"text": "#FFF8E2"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1000,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "celebrate",
|
||||
"speed_ms": 420,
|
||||
"theme": {
|
||||
"bg": "#231038",
|
||||
"accent": "#F4CB4A",
|
||||
"text": "#FFF8E2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1000,
|
||||
"effect": "blink",
|
||||
"speed_ms": 240,
|
||||
"theme": {
|
||||
"bg": "#341A4D",
|
||||
"accent": "#FFE083",
|
||||
"text": "#FFFDF3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "zoom",
|
||||
"duration_ms": 280
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_WINNER",
|
||||
"title": "WINNER",
|
||||
"subtitle": "Mode Winner actif",
|
||||
"symbol": "WIN",
|
||||
"effect": "celebrate",
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 320
|
||||
},
|
||||
"demo": {
|
||||
"mode": "arcade",
|
||||
"particle_count": 3,
|
||||
"strobe_level": 75
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#1D0E33",
|
||||
"accent": "#FFD36C",
|
||||
"text": "#FFF7DE"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1200,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "celebrate",
|
||||
"speed_ms": 320,
|
||||
"theme": {
|
||||
"bg": "#1D0E33",
|
||||
"accent": "#FFD36C",
|
||||
"text": "#FFF7DE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1200,
|
||||
"effect": "pulse",
|
||||
"speed_ms": 260,
|
||||
"theme": {
|
||||
"bg": "#2B1450",
|
||||
"accent": "#FFE39A",
|
||||
"text": "#FFFEF6"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "zoom",
|
||||
"duration_ms": 220
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"id": "SCENE_WIN_ETAPE",
|
||||
"title": "VICTOIRE",
|
||||
"subtitle": "",
|
||||
"symbol": "WIN",
|
||||
"effect": "celebrate",
|
||||
"visual": {
|
||||
"show_title": false,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 420
|
||||
},
|
||||
"theme": {
|
||||
"bg": "#231038",
|
||||
"accent": "#F4CB4A",
|
||||
"text": "#FFF8E2"
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 1000,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "celebrate",
|
||||
"speed_ms": 420,
|
||||
"theme": {
|
||||
"bg": "#231038",
|
||||
"accent": "#F4CB4A",
|
||||
"text": "#FFF8E2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 1000,
|
||||
"effect": "blink",
|
||||
"speed_ms": 240,
|
||||
"theme": {
|
||||
"bg": "#341A4D",
|
||||
"accent": "#FFE083",
|
||||
"text": "#FFFDF3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transition": {
|
||||
"effect": "zoom",
|
||||
"duration_ms": 280
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,105 @@
|
||||
## [2026-02-25] Clean ecrans - reduction du catalogue LittleFS (phase 1)
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260225_202801_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260225_202801_status.txt`
|
||||
- Scope:
|
||||
- `data/story/screens/*.json`
|
||||
- `game/scenarios/scene_editor_all.yaml`
|
||||
- `game/scenarios/scenario_reel_template.yaml`
|
||||
- Actions:
|
||||
- suppression de 9 ecrans non utilises par le runtime principal:
|
||||
- `SCENE_BROKEN`, `SCENE_CAMERA_SCAN`, `SCENE_FIREWORKS`, `SCENE_LA_DETECT`,
|
||||
`SCENE_MEDIA_ARCHIVE`, `SCENE_SIGNAL_SPIKE`, `SCENE_WIN`, `SCENE_WINNER`, `SCENE_WIN_ETAPE`.
|
||||
- regeneration workbench scenes:
|
||||
- `python3 tools/dev/export_scene_editor_workbench.py`
|
||||
- suppression des references YAML `screen_json` obsoletes dans `scenario_reel_template.yaml` (catalogues ecrans + triggers scenes).
|
||||
- Resultat:
|
||||
- `data/story/screens`: 24 -> 15 fichiers.
|
||||
- `scene_editor_all.yaml`: `unused_scene_ids` reduit de 15 -> 6.
|
||||
- Gates/evidence:
|
||||
- `./tools/dev/story-gen validate` ✅
|
||||
- parse JSON `data/story/screens/*.json` ✅ (`json_screens_ok 15`)
|
||||
- check refs `screen_json` YAML manquants ✅ (`missing_screen_json_refs 0`)
|
||||
- `python3 ../../tools/scenario/validate_scenario.py ../../game/scenarios/zacus_v1.yaml` ✅
|
||||
- `python3 ../../tools/scenario/export_md.py ../../game/scenarios/zacus_v1.yaml` ✅
|
||||
- `python3 ../../tools/audio/validate_manifest.py ../../audio/manifests/zacus_v1_audio.yaml` ✅
|
||||
- `python3 ../../tools/printables/validate_manifest.py ../../printables/manifests/zacus_v1_printables.yaml` ✅
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t buildfs` ✅
|
||||
|
||||
## [2026-02-25] Clean ecrans (screens) - sync workbench + gates
|
||||
|
||||
- Scope:
|
||||
- `game/scenarios/scene_editor_all.yaml`
|
||||
- `data/story/screens/*.json`
|
||||
- `data/screens/*.json`
|
||||
- Actions:
|
||||
- regeneration du workbench ecrans depuis les JSON canoniques:
|
||||
- `python3 tools/dev/export_scene_editor_workbench.py`
|
||||
- controle coherence tokens UI (`effect`, `timeline.keyframes[].effect`, `transition.effect`) contre le parseur runtime `ui_manager.cpp`: aucun token invalide detecte.
|
||||
- controle JSON des ecrans (`data/story/screens` + `data/screens`): parse OK.
|
||||
- Resultat:
|
||||
- `game/scenarios/scene_editor_all.yaml` resynchronise avec l'etat reel des ecrans runtime.
|
||||
- aucune derive restante detectee sur les 6 mirrors legacy `data/screens/*.json` (alignes avec `data/story/screens/SCENE_*.json` correspondants).
|
||||
- Gates/evidence:
|
||||
- `./tools/dev/story-gen validate` ✅
|
||||
- `jq empty` sur `data/story/screens/*.json` et `data/screens/*.json` ✅
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t buildfs` ✅
|
||||
|
||||
## [2026-02-25] Clean global scenario/scene (YAML + code + assets)
|
||||
|
||||
- Skills utilises (ordre):
|
||||
- `firmware-story-stack`
|
||||
- `firmware-littlefs-stack`
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260225_200958_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260225_200958_status.txt`
|
||||
- Clean applique:
|
||||
- `lib/zacus_story_gen_ai/src/zacus_story_gen_ai/generator.py`
|
||||
- classification `workbench` ajoutee pour ignorer les YAML d'edition scene (`meta` + `scenes`) dans `story-gen validate`.
|
||||
- resolution alias actions ajoutee pour les noms courts LittleFS:
|
||||
- `ACTION_QR_CODE_SCANNER_START` -> `ACTION_QR_SCAN_START`
|
||||
- `ACTION_SET_BOOT_MEDIA_MANAGER` -> `ACTION_BOOT_MEDIA_MGR`
|
||||
- runtime Freenove:
|
||||
- `ui_freenove_allinone/src/app/main.cpp`
|
||||
- `ui_freenove_allinone/src/main.cpp`
|
||||
- `executeStoryAction` charge maintenant d'abord `ACTION_ID.json`, puis fallback alias fichier court si absent.
|
||||
- Gates/evidence:
|
||||
- `./tools/dev/story-gen validate` ✅ (`scenarios=5`, `game_scenarios=2`)
|
||||
- `./tools/dev/story-gen generate-cpp` ✅ (`spec_hash=aa9658456a0d`)
|
||||
- `./tools/dev/story-gen generate-bundle --out-dir /tmp/zacus_story_bundle_clean_20260225_201347` ✅
|
||||
- verification assets actions generes:
|
||||
- `/tmp/zacus_story_bundle_clean_20260225_201347/story/actions/ACTION_QR_CODE_SCANNER_START.json` (type/config presents)
|
||||
- `/tmp/zacus_story_bundle_clean_20260225_201347/story/actions/ACTION_SET_BOOT_MEDIA_MANAGER.json` (type/config presents)
|
||||
- `pio run -e freenove_esp32s3` ✅
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t buildfs` ✅
|
||||
|
||||
## [2026-02-25] Audit + clean dossier data (story/littlefs)
|
||||
|
||||
- Skills utilises (ordre):
|
||||
- `firmware-story-stack`
|
||||
- `firmware-littlefs-stack`
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260225_195727_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260225_195727_status.txt`
|
||||
- Audit `data/`:
|
||||
- 89 fichiers, 16 dossiers, aucun fichier temporaire suspect detecte.
|
||||
- derive detectee entre fallback legacy `/screens/*` et canonique `/story/screens/*` pour:
|
||||
- `data/screens/la_detector.json`
|
||||
- `data/screens/locked.json`
|
||||
- fichier legacy non reference detecte:
|
||||
- `data/scenarios/data/default_scenario.json`
|
||||
- Clean applique:
|
||||
- synchronisation fallback `/screens` avec les JSON canoniques:
|
||||
- `data/screens/la_detector.json` -> aligne sur `data/story/screens/SCENE_LA_DETECTOR.json`
|
||||
- `data/screens/locked.json` -> aligne sur `data/story/screens/SCENE_LOCKED.json`
|
||||
- suppression du legacy non reference:
|
||||
- `data/scenarios/data/default_scenario.json`
|
||||
- Gates/evidence:
|
||||
- parse JSON complet `data/**/*.json` via `jq empty` ✅
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t buildfs` ✅
|
||||
- note: `./tools/dev/story-gen validate` echoue sur `../../game/scenarios/scene_editor_all.yaml` (format workbench non reconnu), anomalie preexistante hors clean `data/`.
|
||||
|
||||
## [2026-02-25] Preparation test utilisateur reel (runbook)
|
||||
|
||||
- Carte preparee pour test terrain:
|
||||
@@ -2106,3 +2208,26 @@
|
||||
- `~/.codex/skills/scene-verificator/scripts/run_scene_verification.sh /dev/cu.usbmodem5AB90753301 115200` ✅
|
||||
- `~/.codex/skills/fx-verificator/scripts/run_fx_verification.sh /dev/cu.usbmodem5AB90753301 115200` ✅
|
||||
- `~/.codex/skills/hal-verificator-status/scripts/run_hal_verification.sh /dev/cu.usbmodem5AB90753301 115200` ✅
|
||||
|
||||
## Correction SDMMC - diagnostic carte/FS/lecteur (2026-02-25)
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260225-194217_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260225-194217_status.txt`
|
||||
- scan artefacts trackes (`.pio/.platformio/logs/dist/build/node_modules/.venv`) -> aucun tracke.
|
||||
- Cause identifiee:
|
||||
- les lectures `/story/*` passaient par SD en priorite (`readTextFileWithOrigin`), ce qui exposait le runtime aux erreurs `diskio_sdmmc` en cas de carte instable, meme quand LittleFS contenait deja les assets.
|
||||
- Correctif applique (`ui_freenove_allinone/src/storage/storage_manager.cpp`):
|
||||
- fallback runtime passe en **LittleFS d'abord**, SD ensuite (sauf chemin force `/sd/...`),
|
||||
- surveillance `errno` sur `SD_MMC.exists/open/read`,
|
||||
- compteur d'echecs SD (`sd_failure_streak_`) + coupure SD auto apres erreurs I/O repetees,
|
||||
- remount SD explicite tente dans `syncStoryFileFromSd`/`syncStoryTreeFromSd` quand SD etait degrade.
|
||||
- Interface mise a jour:
|
||||
- `ui_freenove_allinone/include/storage/storage_manager.h` (etat SD mutable + helpers de diagnostic).
|
||||
- Build/upload:
|
||||
- `pio run -e freenove_esp32s3` ✅
|
||||
- `pio run -e freenove_esp32s3 -t upload --upload-port /dev/cu.usbmodem5AB90753301` ✅
|
||||
- Validation live:
|
||||
- log diagnostic: `logs/sdmmc_diag_20260225-195139.log`
|
||||
- resultat: `SUMMARY sd_errors=0 panic_markers=0 ack_next_ok=10 ack_next_fail=0`
|
||||
- gate smoke: `python3 tools/dev/serial_smoke.py --role esp32 --port /dev/cu.usbmodem5AB90753301 --baud 115200 --timeout 10 --wait-port 20` ✅
|
||||
|
||||
+4
-1
@@ -40,6 +40,9 @@ class StorageManager {
|
||||
bool copyFileFromSdToLittleFs(const char* src_path, const char* dst_path) const;
|
||||
bool copyStoryDirectoryFromSd(const char* relative_dir);
|
||||
String resolveReadableAssetPath(const String& absolute_path) const;
|
||||
void noteSdAccessFailure(const char* operation, const char* path, int error_code) const;
|
||||
void noteSdAccessSuccess() const;
|
||||
|
||||
bool sd_ready_ = false;
|
||||
mutable bool sd_ready_ = false;
|
||||
mutable uint8_t sd_failure_streak_ = 0U;
|
||||
};
|
||||
|
||||
@@ -2611,7 +2611,22 @@ bool executeStoryAction(const char* action_id, const ScenarioSnapshot& snapshot,
|
||||
}
|
||||
|
||||
const String action_path = String("/story/actions/") + action_id + ".json";
|
||||
const String payload = g_storage.loadTextFile(action_path.c_str());
|
||||
String payload = g_storage.loadTextFile(action_path.c_str());
|
||||
if (payload.isEmpty()) {
|
||||
const char* alias_id = nullptr;
|
||||
if (std::strcmp(action_id, "ACTION_QR_CODE_SCANNER_START") == 0) {
|
||||
alias_id = "ACTION_QR_SCAN_START";
|
||||
} else if (std::strcmp(action_id, "ACTION_SET_BOOT_MEDIA_MANAGER") == 0) {
|
||||
alias_id = "ACTION_BOOT_MEDIA_MGR";
|
||||
}
|
||||
if (alias_id != nullptr) {
|
||||
const String alias_path = String("/story/actions/") + alias_id + ".json";
|
||||
payload = g_storage.loadTextFile(alias_path.c_str());
|
||||
if (!payload.isEmpty()) {
|
||||
Serial.printf("[ACTION] payload alias id=%s file=%s\n", action_id, alias_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
StaticJsonDocument<512> action_doc;
|
||||
if (!payload.isEmpty()) {
|
||||
deserializeJson(action_doc, payload);
|
||||
|
||||
@@ -635,6 +635,12 @@ void ScenarioManager::loadStepResourceOverrides(const char* scenario_file_path)
|
||||
}
|
||||
JsonObjectConst step_obj = variant.as<JsonObjectConst>();
|
||||
const char* step_id = stringOrNull(step_obj["id"]);
|
||||
if (step_id == nullptr) {
|
||||
step_id = stringOrNull(step_obj["step_id"]);
|
||||
}
|
||||
if (step_id == nullptr) {
|
||||
step_id = stringOrNull(step_obj["stepId"]);
|
||||
}
|
||||
if (step_id == nullptr) {
|
||||
continue;
|
||||
}
|
||||
@@ -660,6 +666,9 @@ void ScenarioManager::loadStepResourceOverrides(const char* scenario_file_path)
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["actionIds"].as<JsonArrayConst>();
|
||||
}
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["actions"].as<JsonArrayConst>();
|
||||
}
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["resources"]["action_ids"].as<JsonArrayConst>();
|
||||
}
|
||||
|
||||
@@ -1534,7 +1534,22 @@ bool executeStoryAction(const char* action_id, const ScenarioSnapshot& snapshot,
|
||||
return false;
|
||||
}
|
||||
const String action_path = String("/story/actions/") + action_id + ".json";
|
||||
const String payload = g_storage.loadTextFile(action_path.c_str());
|
||||
String payload = g_storage.loadTextFile(action_path.c_str());
|
||||
if (payload.isEmpty()) {
|
||||
const char* alias_id = nullptr;
|
||||
if (std::strcmp(action_id, "ACTION_QR_CODE_SCANNER_START") == 0) {
|
||||
alias_id = "ACTION_QR_SCAN_START";
|
||||
} else if (std::strcmp(action_id, "ACTION_SET_BOOT_MEDIA_MANAGER") == 0) {
|
||||
alias_id = "ACTION_BOOT_MEDIA_MGR";
|
||||
}
|
||||
if (alias_id != nullptr) {
|
||||
const String alias_path = String("/story/actions/") + alias_id + ".json";
|
||||
payload = g_storage.loadTextFile(alias_path.c_str());
|
||||
if (!payload.isEmpty()) {
|
||||
Serial.printf("[ACTION] payload alias id=%s file=%s\n", action_id, alias_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
StaticJsonDocument<512> action_doc;
|
||||
if (!payload.isEmpty()) {
|
||||
deserializeJson(action_doc, payload);
|
||||
|
||||
@@ -549,6 +549,12 @@ void ScenarioManager::loadStepResourceOverrides(const char* scenario_file_path)
|
||||
}
|
||||
JsonObjectConst step_obj = variant.as<JsonObjectConst>();
|
||||
const char* step_id = stringOrNull(step_obj["id"]);
|
||||
if (step_id == nullptr) {
|
||||
step_id = stringOrNull(step_obj["step_id"]);
|
||||
}
|
||||
if (step_id == nullptr) {
|
||||
step_id = stringOrNull(step_obj["stepId"]);
|
||||
}
|
||||
if (step_id == nullptr) {
|
||||
continue;
|
||||
}
|
||||
@@ -574,6 +580,9 @@ void ScenarioManager::loadStepResourceOverrides(const char* scenario_file_path)
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["actionIds"].as<JsonArrayConst>();
|
||||
}
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["actions"].as<JsonArrayConst>();
|
||||
}
|
||||
if (action_ids.isNull()) {
|
||||
action_ids = step_obj["resources"]["action_ids"].as<JsonArrayConst>();
|
||||
}
|
||||
|
||||
+87
-21
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
|
||||
#include "resources/screen_scene_registry.h"
|
||||
#include "scenarios/default_scenario_v2.h"
|
||||
@@ -49,6 +50,8 @@ constexpr EmbeddedStoryAsset kEmbeddedStoryAssets[] = {
|
||||
{"/story/scenarios/DEFAULT.json", R"JSON({"scenario":"DEFAULT","source":"embedded_minimal"})JSON"},
|
||||
};
|
||||
|
||||
constexpr uint8_t kSdFailureDisableThreshold = 3U;
|
||||
|
||||
|
||||
uint32_t fnv1aUpdate(uint32_t hash, uint8_t value) {
|
||||
hash ^= value;
|
||||
@@ -182,6 +185,7 @@ bool StorageManager::mountSdCard() {
|
||||
}
|
||||
Serial.printf("[FS] SD_MMC mounted size=%lluMB\n",
|
||||
static_cast<unsigned long long>(SD_MMC.cardSize() / (1024ULL * 1024ULL)));
|
||||
sd_failure_streak_ = 0U;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
@@ -249,7 +253,17 @@ bool StorageManager::pathExistsOnSdCard(const char* path) const {
|
||||
if (sd_path.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return SD_MMC.exists(sd_path.c_str());
|
||||
errno = 0;
|
||||
const bool exists = SD_MMC.exists(sd_path.c_str());
|
||||
const int error_code = errno;
|
||||
if (exists) {
|
||||
noteSdAccessSuccess();
|
||||
return true;
|
||||
}
|
||||
if (error_code != 0 && error_code != ENOENT) {
|
||||
noteSdAccessFailure("exists", sd_path.c_str(), error_code);
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
(void)path;
|
||||
return false;
|
||||
@@ -285,22 +299,39 @@ bool StorageManager::readTextFromLittleFs(const char* path, String* out_payload)
|
||||
}
|
||||
|
||||
bool StorageManager::readTextFromSdCard(const char* path, String* out_payload) const {
|
||||
if (out_payload == nullptr || !pathExistsOnSdCard(path)) {
|
||||
if (out_payload == nullptr || !sd_ready_) {
|
||||
return false;
|
||||
}
|
||||
#if ZACUS_HAS_SD_MMC
|
||||
const String sd_path = stripSdPrefix(path);
|
||||
if (sd_path.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
errno = 0;
|
||||
File file = SD_MMC.open(sd_path.c_str(), "r");
|
||||
if (!file) {
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
const int open_error = errno;
|
||||
if (!file || file.isDirectory()) {
|
||||
if (file) {
|
||||
file.close();
|
||||
}
|
||||
if (open_error != 0 && open_error != ENOENT) {
|
||||
noteSdAccessFailure("open", sd_path.c_str(), open_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
out_payload->remove(0);
|
||||
out_payload->reserve(static_cast<size_t>(file.size()) + 1U);
|
||||
while (file.available()) {
|
||||
*out_payload += static_cast<char>(file.read());
|
||||
const int value = file.read();
|
||||
if (value < 0) {
|
||||
file.close();
|
||||
noteSdAccessFailure("read", sd_path.c_str(), EIO);
|
||||
return false;
|
||||
}
|
||||
*out_payload += static_cast<char>(value);
|
||||
}
|
||||
file.close();
|
||||
noteSdAccessSuccess();
|
||||
return !out_payload->isEmpty();
|
||||
#else
|
||||
(void)path;
|
||||
@@ -317,7 +348,6 @@ bool StorageManager::readTextFileWithOrigin(const char* path, String* out_payloa
|
||||
return false;
|
||||
}
|
||||
const bool force_sd = startsWithIgnoreCase(normalized.c_str(), "/sd/");
|
||||
const bool prefer_sd = !force_sd && startsWithIgnoreCase(normalized.c_str(), "/story/");
|
||||
|
||||
String payload;
|
||||
if (force_sd) {
|
||||
@@ -332,13 +362,6 @@ bool StorageManager::readTextFileWithOrigin(const char* path, String* out_payloa
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prefer_sd && readTextFromSdCard(normalized.c_str(), &payload)) {
|
||||
*out_payload = payload;
|
||||
if (out_origin != nullptr) {
|
||||
*out_origin = "/sd" + stripSdPrefix(normalized.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (readTextFromLittleFs(normalized.c_str(), &payload)) {
|
||||
*out_payload = payload;
|
||||
if (out_origin != nullptr) {
|
||||
@@ -576,13 +599,16 @@ bool StorageManager::copyFileFromSdToLittleFs(const char* src_path, const char*
|
||||
}
|
||||
#if ZACUS_HAS_SD_MMC
|
||||
if (!pathExistsOnSdCard(src_path)) {
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
return false;
|
||||
}
|
||||
const String sd_path = stripSdPrefix(src_path);
|
||||
errno = 0;
|
||||
File src = SD_MMC.open(sd_path.c_str(), "r");
|
||||
const int open_error = errno;
|
||||
if (!src) {
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
if (open_error != 0 && open_error != ENOENT) {
|
||||
noteSdAccessFailure("open", sd_path.c_str(), open_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!ensureParentDirectoriesOnLittleFs(dst_path)) {
|
||||
@@ -592,24 +618,26 @@ bool StorageManager::copyFileFromSdToLittleFs(const char* src_path, const char*
|
||||
File dst = LittleFS.open(dst_path, "w");
|
||||
if (!dst) {
|
||||
src.close();
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
return false;
|
||||
}
|
||||
uint8_t buffer[512];
|
||||
while (src.available()) {
|
||||
const size_t read_bytes = src.read(buffer, sizeof(buffer));
|
||||
if (read_bytes == 0U) {
|
||||
break;
|
||||
src.close();
|
||||
dst.close();
|
||||
noteSdAccessFailure("read", sd_path.c_str(), EIO);
|
||||
return false;
|
||||
}
|
||||
if (dst.write(buffer, read_bytes) != read_bytes) {
|
||||
dst.close();
|
||||
src.close();
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dst.close();
|
||||
src.close();
|
||||
noteSdAccessSuccess();
|
||||
return true;
|
||||
#else
|
||||
(void)src_path;
|
||||
@@ -619,7 +647,10 @@ bool StorageManager::copyFileFromSdToLittleFs(const char* src_path, const char*
|
||||
}
|
||||
|
||||
bool StorageManager::syncStoryFileFromSd(const char* story_path) {
|
||||
if (!sd_ready_ || story_path == nullptr || story_path[0] == '\0') {
|
||||
if (story_path == nullptr || story_path[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
if (!sd_ready_ && !mountSdCard()) {
|
||||
return false;
|
||||
}
|
||||
const String normalized = normalizeAbsolutePath(story_path);
|
||||
@@ -634,7 +665,10 @@ bool StorageManager::syncStoryFileFromSd(const char* story_path) {
|
||||
}
|
||||
|
||||
bool StorageManager::copyStoryDirectoryFromSd(const char* relative_dir) {
|
||||
if (!sd_ready_ || relative_dir == nullptr || relative_dir[0] == '\0') {
|
||||
if (relative_dir == nullptr || relative_dir[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
if (!sd_ready_ && !mountSdCard()) {
|
||||
return false;
|
||||
}
|
||||
#if ZACUS_HAS_SD_MMC
|
||||
@@ -692,7 +726,7 @@ bool StorageManager::provisionEmbeddedAsset(const char* path,
|
||||
}
|
||||
|
||||
bool StorageManager::syncStoryTreeFromSd() {
|
||||
if (!sd_ready_) {
|
||||
if (!sd_ready_ && !mountSdCard()) {
|
||||
return false;
|
||||
}
|
||||
const char* story_dirs[] = {"scenarios", "screens", "audio", "apps", "actions"};
|
||||
@@ -760,6 +794,38 @@ bool StorageManager::hasSdCard() const {
|
||||
return sd_ready_;
|
||||
}
|
||||
|
||||
void StorageManager::noteSdAccessFailure(const char* operation, const char* path, int error_code) const {
|
||||
#if ZACUS_HAS_SD_MMC
|
||||
RuntimeMetrics::instance().noteSdError();
|
||||
if (error_code == 0 || error_code == ENOENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sd_failure_streak_ < 255U) {
|
||||
++sd_failure_streak_;
|
||||
}
|
||||
Serial.printf("[FS] SD_MMC %s failed path=%s errno=%d streak=%u\n",
|
||||
operation != nullptr ? operation : "op",
|
||||
path != nullptr ? path : "-",
|
||||
error_code,
|
||||
static_cast<unsigned int>(sd_failure_streak_));
|
||||
|
||||
if (sd_failure_streak_ >= kSdFailureDisableThreshold && sd_ready_) {
|
||||
SD_MMC.end();
|
||||
sd_ready_ = false;
|
||||
Serial.println("[FS] SD_MMC disabled after repeated I/O failures; fallback=LittleFS");
|
||||
}
|
||||
#else
|
||||
(void)operation;
|
||||
(void)path;
|
||||
(void)error_code;
|
||||
#endif
|
||||
}
|
||||
|
||||
void StorageManager::noteSdAccessSuccess() const {
|
||||
sd_failure_streak_ = 0U;
|
||||
}
|
||||
|
||||
uint32_t StorageManager::checksum(const char* path) const {
|
||||
const String normalized = normalizeAbsolutePath(path);
|
||||
if (normalized.isEmpty()) {
|
||||
|
||||
@@ -64,6 +64,12 @@ APP_CPP = {
|
||||
"QR_UNLOCK_APP": "StoryAppType::kQrUnlockApp",
|
||||
}
|
||||
|
||||
ACTION_FILE_ALIASES = {
|
||||
# Keep long action IDs in YAML/runtime while allowing short LittleFS filenames.
|
||||
"ACTION_QR_CODE_SCANNER_START": "ACTION_QR_SCAN_START",
|
||||
"ACTION_SET_BOOT_MEDIA_MANAGER": "ACTION_BOOT_MEDIA_MGR",
|
||||
}
|
||||
|
||||
DEFAULT_SCENE_PROFILE: dict[str, Any] = {
|
||||
"title": "MISSION",
|
||||
"subtitle": "",
|
||||
@@ -966,6 +972,13 @@ def _classify_game_yaml(file_path: Path) -> str:
|
||||
has_runtime = bool(doc.get("initial_step")) and isinstance(doc.get("steps"), list)
|
||||
has_narrative = bool(doc.get("title")) and isinstance(doc.get("stations"), list)
|
||||
has_template = isinstance(doc.get("prompt_input"), dict)
|
||||
has_workbench = isinstance(doc.get("meta"), dict) and isinstance(doc.get("scenes"), list)
|
||||
if has_workbench and not has_runtime and not has_narrative and not has_template:
|
||||
return "workbench"
|
||||
if has_workbench and (has_runtime or has_narrative or has_template):
|
||||
raise StoryGenerationError(
|
||||
f"Ambiguous game scenario type in {file_path}: workbench mixed with runtime/narrative/template keys detected"
|
||||
)
|
||||
if has_template and not has_runtime and not has_narrative:
|
||||
return "template"
|
||||
if has_runtime and has_narrative:
|
||||
@@ -1145,6 +1158,11 @@ def _resource_candidates(resource_root: Path, resource_type: str, resource_id: s
|
||||
elif resource_type == "audio":
|
||||
slug = _resource_slug(resource_id, "PACK_")
|
||||
candidates.append(base / f"{slug}.json")
|
||||
elif resource_type == "actions":
|
||||
alias = ACTION_FILE_ALIASES.get(resource_id)
|
||||
if alias:
|
||||
candidates.append(base / f"{alias}.json")
|
||||
candidates.append(base / f"{alias.lower()}.json")
|
||||
|
||||
# de-duplicate while preserving order
|
||||
dedup: list[Path] = []
|
||||
@@ -1629,6 +1647,8 @@ def load_and_validate(paths: StoryPaths, spec_dir: Path | None = None, game_dir:
|
||||
narrative_game_files.append(file_path)
|
||||
elif category == "template":
|
||||
template_files.append(file_path)
|
||||
elif category == "workbench":
|
||||
continue
|
||||
|
||||
_validate_yamale(_schema_path("story_spec_schema.yamale"), spec_files)
|
||||
if runtime_game_files:
|
||||
|
||||
Reference in New Issue
Block a user