Merge origin/main into chore/harmonize-zacus

This commit is contained in:
Clément SAILLANT
2026-02-14 00:21:06 +01:00
28 changed files with 328 additions and 0 deletions
+13
View File
@@ -3,3 +3,16 @@
# Local IDE settings
.vscode/
# Generated game exports
game/exports/*
!game/exports/.gitkeep
!game/exports/previews/
# Locally generated audio/render outputs (not versioned)
audio/generated/*
!audio/generated/README.md
# Locally generated printable PDFs (not versioned)
printables/export/pdf/zacus_v1/*
!printables/export/pdf/zacus_v1/README.md
+24
View File
@@ -0,0 +1,24 @@
# Pipeline audio Zacus v1
Objectif: pipeline local IA-friendly sans dépendance cloud obligatoire.
## Outils cibles
- TTS: `espeak`, `mbrola`, ou `piper`
- Conversion: `ffmpeg` (MP3 mono faible débit)
## Convention export
- Mono, 22.05 kHz ou 44.1 kHz
- 4896 kbps
- Nommage depuis `audio/manifests/zacus_v1_audio.yaml`
## Étapes
1. Générer voix (hotline + messages U-SON).
2. Générer textures/musiques via prompts (voir `game/prompts/audio/`).
3. Normaliser volume.
4. Valider présence des fichiers avec `tools/audio/validate_manifest.py`.
## Génération locale rapide (placeholder)
Si vous voulez débloquer rapidement des tests sans TTS externe:
- `python3 tools/audio/generate_local_assets.py`
Ce script crée `audio/generated/*` en réutilisant des MP3 présents dans le repo (placeholders techniques).
+11
View File
@@ -0,0 +1,11 @@
# audio/generated
Ce dossier contient des artefacts audio générés localement (MP3), non versionnés.
Pourquoi: plusieurs workflows d'extraction/patch (et revues diff textuelles) ne gèrent pas bien les binaires.
Génération locale:
- `python3 tools/audio/generate_local_assets.py`
Validation:
- `python3 tools/audio/validate_manifest.py`
+8
View File
@@ -0,0 +1,8 @@
# Glossaire
- **U-SON**: module audio narratif principal.
- **U-LOCK**: verrouillage/déverrouillage (souvent via LA).
- **LA**: note de référence à produire à l’étape 5.
- **HOTLINE**: canal Brigade Z (Indice / Validation / Relance).
- **Brigade Z**: cellule fictive de support enquête.
- **Morse partiel**: indice transcodé via oscilloscope/table.
View File
+4
View File
@@ -0,0 +1,4 @@
# Prompts IA du jeu
- Audio: `game/prompts/audio/`
- Printables: voir `printables/src/prompts/`
@@ -0,0 +1,10 @@
# Prompts musique / ambiance
## Ambiance labo enquête 2030 min
"Musique électronique légère, rétro-futuriste, tempo modéré, textures de laboratoire, petits bips scientifiques, sans voix, répétition douce, adaptée à un jeu denquête enfant 911 ans."
## Boucle 1h
"Longue boucle ambient techno-lab, peu intrusive, variations subtiles toutes 24 minutes, pas de rupture brutale, mix mono-compatible."
## Message radio brouillé zone 5
"Texture radio AM bruitée + voix distante répétant 'au-dessus du sol...', avec motifs fréquentiels et sensation dindice caché."
+9
View File
@@ -0,0 +1,9 @@
# Prompt TTS Hotline Brigade Z
Voix: française claire, rassurante, dynamique.
Contraintes: phrases 612 mots, articulation nette, pas d'effet robotique exagéré.
Lignes types:
1. "Brigade Z. Hypothèse reçue. Continuez lanalyse."
2. "Indice confirmé. Cherchez la lettre cachée dans le signal."
3. "Relance Brigade Z. Reprenez QUI, OU, COMMENT."
+3
View File
@@ -0,0 +1,3 @@
# Schema
Réservé pour futurs schémas YAML/JSON du canon scénario.
+24
View File
@@ -0,0 +1,24 @@
# Intégration scénario Zacus v1 <-> hardware
## Mapping gameplay -> devices
- **U-LOCK**: verrou dentrée mission; déverrouillage vocal/ton LA.
- **U-SON**: boot corrompu, progression, jingle victoire.
- **Oscilloscope**: sinus ETAPE 1, morse partiel ETAPE 4.
- **Téléphones zones**: indices tokens L / AFO / EFO / LE / U.
- **Téléphone hotline (salon)**: validation/indice/relance Brigade Z.
## Préparer la SD
Exemple structure:
- `/story/` specs story v2 compilées
- `/audio/uson/` fichiers U-SON
- `/audio/hotline/` répliques courtes
- `/audio/zones/` radios brouillées
- `/audio/ambiances/` longues boucles
Nommage conseillé: `zacus_v1_<device>_<cue>_v01.mp3`
## ETAPE 2 (delay story)
- Départ: état verrouillé.
- Après déverrouillage + délai paramétré: step `ETAPE2_HINT`.
- Action: jouer hint audio salon + garder gate MP3 fermé.
- Puis passage `STEP_DONE` pour ouvrir gate MP3 (selon scénario choisi).
@@ -0,0 +1,68 @@
id: ZACUS_V1_UNLOCK_ETAPE2
version: 2
initial_step: STEP_WAIT_UNLOCK
app_bindings:
- id: APP_LA
app: LA_DETECTOR
config:
hold_ms: 3000
unlock_event: UNLOCK
require_listening: true
- id: APP_AUDIO
app: AUDIO_PACK
- id: APP_SCREEN
app: SCREEN_SCENE
- id: APP_GATE
app: MP3_GATE
steps:
- step_id: STEP_WAIT_UNLOCK
screen_scene_id: SCENE_LOCKED
audio_pack_id: ""
actions: [ACTION_TRACE_STEP]
apps: [APP_LA, APP_SCREEN, APP_GATE]
mp3_gate_open: false
transitions:
- trigger: on_event
event_type: unlock
event_name: UNLOCK
target_step_id: STEP_UNLOCK_WIN
after_ms: 0
priority: 100
- step_id: STEP_UNLOCK_WIN
screen_scene_id: SCENE_REWARD
audio_pack_id: PACK_WIN
actions: [ACTION_TRACE_STEP]
apps: [APP_AUDIO, APP_SCREEN, APP_GATE]
mp3_gate_open: false
transitions:
- trigger: on_event
event_type: audio_done
event_name: AUDIO_DONE
target_step_id: ETAPE2_HINT
after_ms: 0
priority: 100
- step_id: ETAPE2_HINT
screen_scene_id: SCENE_INFO
audio_pack_id: PACK_ETAPE2_HINT
actions: [ACTION_TRACE_STEP]
apps: [APP_AUDIO, APP_SCREEN, APP_GATE]
mp3_gate_open: false
transitions:
- trigger: after_ms
event_type: timer
event_name: DELAY_ETAPE2
target_step_id: STEP_DONE
after_ms: 12000
priority: 90
- step_id: STEP_DONE
screen_scene_id: SCENE_READY
audio_pack_id: ""
actions: [ACTION_TRACE_STEP, ACTION_REFRESH_SD]
apps: [APP_SCREEN, APP_GATE]
mp3_gate_open: true
transitions: []
+8
View File
@@ -0,0 +1,8 @@
# printables/export/pdf/zacus_v1
Ce dossier contient des PDF générés localement, non versionnés.
Pourquoi: les binaires PDF ne sont pas adaptés aux diff/patch textuels et aux étapes d'extraction automatisée.
Génération locale:
- `python3 tools/printables/generate_local_pdf_placeholders.py`
@@ -0,0 +1,7 @@
# Placeholder cartes (structure)
+---------------------------+
| [ZONE Z3] Atelier |
| Responsable: Dr Oscilla |
| Indice: signal sinus |
+---------------------------+
@@ -0,0 +1,6 @@
# Placeholder feuille enquête
QUI : ______________________
OU : ______________________
COMMENT: _____________________
PREUVE: ______________________
+7
View File
@@ -0,0 +1,7 @@
# Prompt packs IA — printables Zacus v1
Tous les prompts imposent:
- style "Cluedo premium × labo rétro-futuriste"
- lisibilité N&B
- sans logos/marques externes
- marges de sécurité + fond perdu
@@ -0,0 +1,3 @@
Variables: ZONE_NAME, COLOR, ICON, SHORT_DESC
Format: A4 portrait.
Prompt: Affiche zone immersive avec titre massif, code couleur adaptable, texture labo, lisible de loin, version N&B sans perte d'information.
@@ -0,0 +1,3 @@
Variables: DETECTIVE_NAME, TEAM_COLOR, BADGE_ID
Format: A6 (2 badges par page A4 possible).
Prompt: Badge détective rétro-futuriste avec zone prénom manuscrite, code badge mono, contraste imprimable.
@@ -0,0 +1,3 @@
Variables: ZONE_ID, ZONE_NAME, COLOR, HOME_LOCATION, FEATURE
Format: A6 paysage.
Prompt: Carte lieu avec code zone, texture papier technique, cadre acier, icônes de station, lisibilité forte en impression monochrome.
@@ -0,0 +1,3 @@
Variables: ITEM_NAME, ITEM_TYPE, CLUE_TEXT, COLOR
Format: A6 portrait.
Prompt: Carte objet/indice style dossier d'enquête premium, encarts codés, numéro de preuve, sans marque tierce.
@@ -0,0 +1,3 @@
Variables: SUSPECT_NAME, ROLE, GIMMICK, ALIBI, SUSPICION_LEVEL, COLOR
Format: A6 portrait.
Prompt: Carte suspect avec portrait stylisé non-marqué, bandeau nom, bloc alibi, pictogrammes labo, trame circuits fine, déclinable N&B.
@@ -0,0 +1,3 @@
Variables: TEAM_NAME, STEP_LABEL
Format: A4 portrait.
Prompt: Feuille enquête QUI/OU/COMMENT avec grilles claires, zones de notes, rappel hotline, hiérarchie typographique robuste N&B.
+3
View File
@@ -0,0 +1,3 @@
Variables: HOTLINE_NAME, CALL_TYPES, TOKEN_SET
Format: A4 portrait.
Prompt: Affiche hotline Brigade Z avec script court (Indice/Validation/Relance), tableau tokens téléphones L-AFO-EFO-LE-U, interdiction logos externes.
+3
View File
@@ -0,0 +1,3 @@
Variables: EVENT_DATE, PLAYER_NAME, LOCATION, DRESSCODE
Format: A6 recto/verso, fond perdu 3 mm, marge de sécurité 5 mm.
Prompt: Carte invitation rétro-futuriste, cadre métal, vis de coin, bandeau noir/doré, couleurs or discrètes. Inclure zone texte lisible en N&B, contraste élevé, typo titrage Bebas Neue, corps Inter. Interdiction de logos/marques.
+3
View File
@@ -0,0 +1,3 @@
Variables: DURATION, TEAM_COUNT, HOTLINE_RULE
Format: A4 1 page.
Prompt: Règles de jeu enfant 9-11 ans, étapes simples, icônes claires, encadré sécurité, design premium mais sobre N&B.
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
from pathlib import Path
import shutil
ROOT = Path(__file__).resolve().parents[2]
out = ROOT / 'audio' / 'generated'
out.mkdir(parents=True, exist_ok=True)
source_pool = {
'uson_boot_corrompu_30s.mp3': ROOT / 'hardware/firmware/esp32/data/uson_boot_corrompu_lowmono.mp3',
'uson_win_15s.mp3': ROOT / 'hardware/firmware/esp32/data/uson_mode_hero.mp3',
'zone5_radio_brouille_v1_30s.mp3': ROOT / 'hardware/firmware/esp32/data/uson_etape1_radio_lowmono.mp3',
'zone5_radio_brouille_v2_45s.mp3': ROOT / 'hardware/firmware/esp32/sonGPT/uson_etape1_radio_lointaine_audible.mp3',
'hotline_validation_01.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
'hotline_indice_01.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
'hotline_relance_01.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
'hotline_validation_02.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
'hotline_indice_02.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
'hotline_relance_02.mp3': ROOT / 'hardware/firmware/esp32/data/uson_unlock_rapport_imprime.mp3',
}
for name, src in source_pool.items():
if not src.exists():
raise SystemExit(f"Missing source asset: {src}")
dst = out / name
shutil.copyfile(src, dst)
print(f"generated {dst.relative_to(ROOT)} <- {src.relative_to(ROOT)}")
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / 'printables' / 'export' / 'pdf' / 'zacus_v1'
OUT.mkdir(parents=True, exist_ok=True)
# Minimal PDF writer (no external dependency)
def pdf_escape(s: str) -> str:
return s.replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)')
def write_simple_pdf(path: Path, title: str, lines: list[str]):
content_lines = [
'BT',
'/F1 18 Tf',
'50 790 Td',
f'({pdf_escape(title)}) Tj',
'/F1 11 Tf',
]
y = 760
for ln in lines:
content_lines.append(f'1 0 0 1 50 {y} Tm ({pdf_escape(ln)}) Tj')
y -= 16
if y < 60:
break
content_lines.append('ET')
stream = '\n'.join(content_lines).encode('latin-1', errors='replace')
objs = []
objs.append(b'1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n')
objs.append(b'2 0 obj << /Type /Pages /Count 1 /Kids [3 0 R] >> endobj\n')
objs.append(b'3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj\n')
objs.append(b'4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n')
objs.append(f'5 0 obj << /Length {len(stream)} >> stream\n'.encode() + stream + b'\nendstream endobj\n')
header = b'%PDF-1.4\n%\xe2\xe3\xcf\xd3\n'
xref_positions = [0]
body = b''
offset = len(header)
for obj in objs:
xref_positions.append(offset)
body += obj
offset += len(obj)
xref = f'xref\n0 {len(xref_positions)}\n'.encode()
xref += b'0000000000 65535 f \n'
for pos in xref_positions[1:]:
xref += f'{pos:010d} 00000 n \n'.encode()
trailer = f'trailer << /Size {len(xref_positions)} /Root 1 0 R >>\nstartxref\n{offset}\n%%EOF\n'.encode()
path.write_bytes(header + body + xref + trailer)
cards = [
('zacus_v1_regles_a4_v01.pdf', 'Regles Zacus v1', [
'Duree 60-90 min | Age 9-11 ans | 6-14 enfants',
'1) Enquete en equipes 2) Hotline Brigade Z 3) Resolution finale',
'Lisibilite N&B prioritaire, sans logos externes.'
]),
('zacus_v1_hotline_a4_v01.pdf', 'Hotline Brigade Z', [
'Reponses autorisees: Indice / Validation / Relance',
'Tokens telephones: L - AFO - EFO - LE - U',
'Solution finale unique: Professeur Electron'
]),
('zacus_v1_feuille_enquete_a4_v01.pdf', 'Feuille Enquete QUI/OU/COMMENT', [
'QUI : ______________________',
'OU : _______________________',
'COMMENT : __________________',
'PREUVE : ___________________'
]),
]
for filename, title, lines in cards:
write_simple_pdf(OUT / filename, title, lines)
print('generated', (OUT / filename).relative_to(ROOT))