chore: coherence pass (exports, manifests, validators, docs, licenses, CI)

This commit is contained in:
Clément SAILLANT
2026-02-14 00:45:15 +01:00
parent 6e213f578e
commit d63165969d
27 changed files with 708 additions and 15 deletions
+25
View File
@@ -0,0 +1,25 @@
name: Validate Zacus content
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: python -m pip install --upgrade pip pyyaml
- name: Validate scenario
run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
- name: Validate audio manifest
run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml
- name: Validate printables manifest
run: python tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml
+12
View File
@@ -0,0 +1,12 @@
# Guidelines for contributors
- **Single source of truth**: `game/scenarios/*.yaml` describe every canonical story point (stations, puzzles, solution, ids). Update the YAML first, then regenerate derived materials.
- **Read-only firmware**: `hardware/firmware/esp32/` is maintained elsewhere; do not edit files, docs, or logs there. Treat it as immutable for this project.
- **Standard workflow**:
1. Edit `game/scenarios/zacus_v1.yaml` (or add a new scenario file).
2. Run `python3 tools/scenario/validate_scenario.py <scenario>`.
3. Run `python3 tools/scenario/export_md.py <scenario>` to regenerate `_generated` outputs.
4. Run `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml`.
5. Run `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml`.
6. Optionally use `make` targets: `make scenario-validate`, `make export`, `make audio-validate`, `make printables-validate`, `make all-validate`.
- **Dependencies**: install PyYAML (`pip install pyyaml` or via a virtual environment such as `python3 -m venv .venv`); each script will error with a reminder if PyYAML is missing.
+5 -7
View File
@@ -1,13 +1,11 @@
# Changelog
## [Unreleased]
- Kit MJ : script minute-par-minute, solution complète, checklist matériel, plan des stations, guide anti-chaos et distribution des rôles prêts à lusage pour 614 enfants pendant 6090 minutes.
- Printables : structure `src/` + `export/{pdf,png}/` maintenue, prompts graphiques dans `src/prompts/`, workflow documenté dans `WORKFLOW.md`, et réorganisation des aperçus (`printables/export/png/{general,fiche-enquete,personnages,zones}`).
- Game & audio : scénario canon `game/scenarios/zacus_v1.yaml` (canon, solution unique), manifestes audio `audio/manifests/zacus_v1_audio.yaml` et prompts narratifs (`game/prompts/audio/intro.md`, `incident.md`, `accusation.md`, `solution.md`).
- Outils : scripts de validation `tools/scenario/validate_scenario.py` et `tools/audio/validate_manifest.py` pour sécuriser chaque nouvelle piste narrative ou audio.
- Documentation & guides : `docs/QUICKSTART.md`, `docs/STYLEGUIDE.md`, `docs/index.md`, `docs/repo-status.md`, et `printables/WORKFLOW.md` donnent un chemin clair pour déployer le kit, rédiger les scénarios et maintenir la cohérence.
- Licences : texte homogène CC BY-NC 4.0 pour les contenus créatifs et MIT pour les codes, avec les fichiers associés (`LICENSES/CC-BY-NC-4.0.txt`, `LICENSES/MIT.txt`), et `README.md` + `CONTRIBUTING.md` + `LICENSE.md` alignés sur cette répartition.
- Références annexes : `include-humain-IA/` (et `include-humain-IA/version-finale/`) renommés pour la portabilité, et la section `docs/repo-status.md` mise à jour pour documenter ces changements.
- Workflow : nouvelle exportation `tools/scenario/export_md.py` et briefs Markdown (kit + `docs/_generated/SCENARIO_BRIEF.md`) alignés sur `game/scenarios/zacus_v1.yaml`.
- Printables : manifeste `printables/manifests/zacus_v1_printables.yaml`, prompts dédiés pour chaque asset et `tools/printables/validate_manifest.py` pour éviter les trous entre IDs et fichiers.
- Documentation : AGENTS, WORKFLOWS, GLOSSARY, Quickstart, index et le Makefile rappellent que le YAML est la single source of truth et listent les commandes standard de validation/export.
- Tooling : `tools/scenario/validate_scenario.py`, `tools/audio/validate_manifest.py`, `tools/scenario/export_md.py` et `tools/printables/validate_manifest.py` accompagnés par `Makefile` facilitent la maintenance.
- Automation : workflow GitHub `.github/workflows/validate.yml` installe PyYAML puis lance les validations de scénario, audio et printables.
## [0.2.0] - 2026-02-12
+18
View File
@@ -0,0 +1,18 @@
PYTHON ?= python3
.PHONY: scenario-validate audio-validate printables-validate export all-validate
scenario-validate:
$(PYTHON) tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
audio-validate:
$(PYTHON) tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml
printables-validate:
$(PYTHON) tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml
export:
$(PYTHON) tools/scenario/export_md.py game/scenarios/zacus_v1.yaml
all-validate: scenario-validate audio-validate printables-validate
@echo "All validations passed."
+18 -7
View File
@@ -1,8 +1,19 @@
# Glossaire
# Glossaire des identifiants
- **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.
## Scénario
- `zacus_v1` : scénario canon servant de single source of truth pour les stations, puzzles, solution (voir `game/scenarios/zacus_v1.yaml`).
- `solution_unique`: champ booléen garantissant quun seul coupable est cohérent avec les preuves.
## Audio
- `intro`, `incident`, `accusation`, `solution` : identifiants des pistes dans `audio/manifests/zacus_v1_audio.yaml` qui pointent vers `game/prompts/audio/*.md`.
## Printables
Chaque `id` du manifeste `printables/manifests/zacus_v1_printables.yaml` a un prompt associé dans `printables/src/prompts/` :
- Invitations A6 : `invitation_a6_recto`, `invitation_a6_verso``invitation_recto.md`, `invitation_verso.md`.
- Cartes A6 : `carte_personnage_a6`, `carte_lieu_a6`, `carte_objet_a6``card_personnage.md`, `card_lieu.md`, `card_objet.md`.
- Feuille denquête : `fiche_enquete_a4``fiche_enquete.md`.
- Badge détective : `badge_detective_a6``badge_detective.md`.
- Règles A4 : `regles_a4``regles.md`.
- Hotline et affiches zones : `hotline_a4`, `affiches_zone_a4_z1``affiches_zone_a4_z6``hotline.md`, `zone_affiche.md`.
> Note : le `tools/printables/validate_manifest.py` vérifie que chaque `prompt` existe et reste aligné avec le YAML.
+4 -1
View File
@@ -1,12 +1,13 @@
# Quickstart
## En un coup d’œil
- Source de vérité : `game/scenarios/zacus_v1.yaml` (dérive tout le reste : kit MJ, audio, printables).
- Public : 6 à 14 enfants, 6090 minutes.
- Matériel : `kit-maitre-du-jeu/`, `printables/`, `game/scenarios/zacus_v1.yaml`, `audio/manifests/zacus_v1_audio.yaml`.
- Licences : contenus créatifs CC BY-NC 4.0 (`LICENSES/CC-BY-NC-4.0.txt`), code/script MIT (`LICENSES/MIT.txt`).
## Installer
1. Copie les imprimables depuis `printables/export/pdf/` ou génère de nouveaux documents via `src/` + `WORKFLOW.md`.
1. Copie les imprimables depuis `printables/export/pdf/` ou généré via les prompts listés dans `printables/manifests/zacus_v1_printables.yaml` + `printables/src/prompts/`.
2. Prépare un lecteur audio avec `audio/manifests/zacus_v1_audio.yaml` et les fichiers de `game/prompts/audio/`.
3. Positionne les stations selon `kit-maitre-du-jeu/plan-stations-et-mise-en-place.md`, prépare les rôles (`distribution-des-roles.md`) et laccueil rapide (`script-minute-par-minute.md`).
@@ -18,6 +19,8 @@
## Outils de validation
- Scénario : `python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml`
- Audio : `python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml`
- Printables : `python tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml`
- Export Markdown : `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml`
## Pour aller plus loin
- Crée une variante : duplique le YAML, modifie `canon` et `solution` et revalide avec le script.
+7
View File
@@ -0,0 +1,7 @@
# Workflows
1. **Mettre à jour le YAML canon** (`game/scenarios/zacus_v1.yaml` ou nouvel ID). Les stations/puzzles/solution sont la source des prompts audio et printables.
2. **Valider le scénario** avec `tools/scenario/validate_scenario.py` pour garantir structure, plages 614 enfants et `solution_unique`.
3. **Exporter les briefings Markdown** via `tools/scenario/export_md.py` pour mettre à jour `kit-maitre-du-jeu/_generated/` et `docs/_generated/SCENARIO_BRIEF.md`.
4. **Valider laudio** avec `tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` afin que chaque piste corresponde à un fichier de `game/prompts/audio/`.
5. **Valider les printables** avec `tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` pour sassurer que chaque ID a un prompt accessible.
+49
View File
@@ -0,0 +1,49 @@
# SCENARIO BRIEF — Le mystère du Professeur Zacus
## Stats
- Joueurs : 614
- Durée : 6090 min
- Solution unique : True
## Introduction
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence.
## Chronologie
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'
- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement.
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants.
## Stations
- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore. | Indice : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret.
- **Salle des archives** — comparer les relevés de sécurité. | Indice : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu.
- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc. | Indice : lempreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté.
- **Carnet du labo secret** — remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. | Indice : Le carnet confirme que « Auréole » génère un signal au-dessus du sol. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4).
## Puzzles
- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé.
Effet : Déclenche le message audio de la station suivante.
- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes.
Effet : Révèle lemplacement du prototype caché.
- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants.
Effet : Confirme le mouvement de la cible et le fait que la porte sest ouverte manuellement.
- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
Effet : Donne le mobile moral unique et pousse vers laccusation finale.
## Solution
- Coupable : Lila Caron, lassistante en chef chargée des capteurs et du prototype.
- Mobile : Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains quelle connaissait mieux que quiconque. Elle craignait que la direction confie lAuréole à un consortium de sécurité sans restrictions éthiques.
- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice.
- Preuves:
- Lempreinte de gants est celle quelle porte toujours.
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur.
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire.
## Notes
En version facile, centre-toi sur le code sonore et la note du tableau ; en version longue, demande aux enfants de rédiger une lettre où ils décrivent pourquoi la protection de lAuréole était essentielle.
+3
View File
@@ -5,11 +5,14 @@
- Scénario canon : `game/scenarios/zacus_v1.yaml`
- Printables & workflow : `printables/` + `printables/WORKFLOW.md`
- Audio canon : `audio/manifests/zacus_v1_audio.yaml` + `game/prompts/audio/`
- Brief scénarios : `docs/_generated/SCENARIO_BRIEF.md`
- Maintenance : `docs/maintenance-repo.md`, `docs/repo-audit.md`, `docs/repo-status.md`
## Guides à consulter
- Quickstart (déploiement terrain) : `docs/QUICKSTART.md`
- Styleguide (ton & structure) : `docs/STYLEGUIDE.md`
- Workflows (validate/export) : `docs/WORKFLOWS.md`
- Glossaire des identifiants : `docs/GLOSSARY.md`
- Contribuer (workflow & licence) : `CONTRIBUTING.md`
## Astuce
@@ -0,0 +1,13 @@
# Guide anti-chaos généré
## Règles
- Max 14 enfants : rotation toutes les 15 min.
- Durée cible 6090 min : deux pauses courtes.
- Un seul enfant parle à la fois, signal sonore pour les transitions.
- Indices dans leurs enveloppes jusqu’à validation du chef de piste.
## Interventions
- Propose un mini-défi express si ça déraille.
- Donne un rôle dobservateur aux enfants en retrait.
+23
View File
@@ -0,0 +1,23 @@
# Checklist scénario
## Configurer
- Scénario : Le mystère du Professeur Zacus
- Fichier source : game/scenarios/zacus_v1.yaml
- Participants : 614
- Durée : 6090 min
- Stations prévues : 4
- Puzzles : 4
- Preuves listées : 3
## Matériel recommandé
- Enveloppes numérotées
- Badges détective + fiches denquête
- Audio : audio/manifests/zacus_v1_audio.yaml
- Printables : suivre printables/manifests/zacus_v1_printables.yaml
## Vérifications
- python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
- python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml
+17
View File
@@ -0,0 +1,17 @@
# Répartition des rôles
## Rôles principaux
- Chef de piste : centralise les indices et relit la timeline.
- Chronométreur : marque les phases (exploration, synthèse, accusation).
- Analyste technique : gère les codes et les puzzles logiques.
- Archiviste : organise les preuves (empreintes, logs, mobile).
- Témoin narrateur : redonne les consignes anti-chaos et raconte.
- Gardien des objets : protège les enveloppes, badges et fiches.
## Rôles optionnels
- Détective des émotions : remet le calme si besoin.
- Cartographe : trace le parcours des stations.
- Ambassadeur : valide que rien na été oublié.
- Journaliste : consigne la conclusion finale.
+39
View File
@@ -0,0 +1,39 @@
# Script — Le mystère du Professeur Zacus
## Introduction
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence.
## Chronologie canon
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'
- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement.
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants.
## Stations
- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore.
- **Salle des archives** — comparer les relevés de sécurité.
- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc.
- **Carnet du labo secret** — remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible.
## Puzzles
- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé.
- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes.
- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants.
- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
## Solution canon
- Coupable : Lila Caron, lassistante en chef chargée des capteurs et du prototype.
- Mobile : Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains quelle connaissait mieux que quiconque. Elle craignait que la direction confie lAuréole à un consortium de sécurité sans restrictions éthiques.
- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice.
- Preuves :
- Lempreinte de gants est celle quelle porte toujours.
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur.
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire.
## Notes pratiques
- Solution unique : True
+35
View File
@@ -0,0 +1,35 @@
# Stations détaillées
## Atelier des ondes
- Focus : stabiliser les lecteurs de fréquences et retrouver le code sonore.
- Clue : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret.
## Salle des archives
- Focus : comparer les relevés de sécurité.
- Clue : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu.
## Bureau du Professeur
- Focus : décrypter la note effacée sur le tableau blanc.
- Clue : lempreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté.
## Carnet du labo secret
- Focus : remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible.
- Clue : Le carnet confirme que « Auréole » génère un signal au-dessus du sol. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4).
## Puzzles
- code-sonore (logique) : Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé.
Effet : Déclenche le message audio de la station suivante.
- log-verifie (observation) : Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes.
Effet : Révèle lemplacement du prototype caché.
- empreinte (tactile) : Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants.
Effet : Confirme le mouvement de la cible et le fait que la porte sest ouverte manuellement.
- mobile (narration) : Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
Effet : Donne le mobile moral unique et pousse vers laccusation finale.
@@ -0,0 +1,72 @@
manifest_id: zacus_v1_printables
version: 1
scenario_id: zacus_v1
items:
- id: invitation_a6_recto
category: invitation
format: a6
sides: recto
prompt: src/prompts/invitation_recto.md
- id: invitation_a6_verso
category: invitation
format: a6
sides: verso
prompt: src/prompts/invitation_verso.md
- id: carte_personnage_a6
category: personnage
format: a6
prompt: src/prompts/card_personnage.md
- id: carte_lieu_a6
category: lieu
format: a6
prompt: src/prompts/card_lieu.md
- id: carte_objet_a6
category: indice
format: a6
prompt: src/prompts/card_objet.md
- id: fiche_enquete_a4
category: fiche_enquete
format: a4
prompt: src/prompts/fiche_enquete.md
- id: badge_detective_a6
category: badge
format: a6
prompt: src/prompts/badge_detective.md
- id: regles_a4
category: regles
format: a4
prompt: src/prompts/regles.md
- id: hotline_a4
category: hotline
format: a4
prompt: src/prompts/hotline.md
- id: affiches_zone_a4_z1
category: zone
format: a4
variant: Z1
prompt: src/prompts/zone_affiche.md
- id: affiches_zone_a4_z2
category: zone
format: a4
variant: Z2
prompt: src/prompts/zone_affiche.md
- id: affiches_zone_a4_z3
category: zone
format: a4
variant: Z3
prompt: src/prompts/zone_affiche.md
- id: affiches_zone_a4_z4
category: zone
format: a4
variant: Z4
prompt: src/prompts/zone_affiche.md
- id: affiches_zone_a4_z5
category: zone
format: a4
variant: Z5
prompt: src/prompts/zone_affiche.md
- id: affiches_zone_a4_z6
category: zone
format: a4
variant: Z6
prompt: src/prompts/zone_affiche.md
@@ -0,0 +1,5 @@
# Prompt : badge détective
- Format : A6, orientation paysage.
- Contenu : titre « Badge détective », case prénom + rôle + symbole loupe.
- Ajoute un micro-fond quadrillé pour les enfants qui veulent noter une preuve rapide.
+5
View File
@@ -0,0 +1,5 @@
# Prompt : carte lieu
- Format : A6, zone dédiée (Atelier des ondes, Salle des archives, Bureau, Carnet secret).
- Inclure un titre en capitales, un sous-titre décrivant les ressources disponibles et un encart « Indice clé ».
- Ajoute des repères graphiques : plan petit, flèches directionnelles, icône de porte.
+6
View File
@@ -0,0 +1,6 @@
# Prompt : carte objet / indice
- Format : A6, orientation portrait, titre « Objet » et subtitre « Indice ».
- Visual : montre un objet (stylo, partition, gants) avec des lignes de note autour.
- Ajoute une section « Effet » qui décrit le progrès de lenquête (ex. « Déclenche laudio de la station suivante »).
- Inclure un mini-tableau « Confirmé par » pour noter les preuves.
@@ -0,0 +1,6 @@
# Prompt : carte personnage
- Format : A6, recto/verso si besoin.
- Contenu : nom (ex. Lila Caron), rôle (assistante capteurs), attribut (gants) et question « Pourquoi protège-t-elle lAuréole ? ».
- Ajoute icône de badge détective et une zone « preuve observée » avec cases à cocher.
- Utilise une typographie claire, lignes horizontales et icône profil simple.
+5
View File
@@ -0,0 +1,5 @@
# Prompt : fiche denquête
- Format : A4, structure deux colonnes (preuves / hypothèses) + zone « Chronologie ».
- Section « Équipe » pour noter les noms + rôle (chef de piste, analyste, etc.).
- Ajoute un encart « Questions ouvertes » avec lignes numérotées.
+5
View File
@@ -0,0 +1,5 @@
# Prompt : hotline
- Format : A4, couleurs limitées, accent sur « Ligne directe Professeur Zacus ».
- Texte : numéro fictif, instructions pour appeler en cas de blocage (ex. « code 1-4-3 sur l’échelle »).
- Ajoute un encart QR pour écouter lenregistrement audio.
@@ -0,0 +1,8 @@
# Prompt : invitation recto
- Format : A6, recto uniquement, marges symétriques.
- Style : titre « Enquête Zacus » en majuscules, sous-titre explicite « Le Professeur a disparu ».
- Ajoute un petit pictogramme de loupe et un bandeau « 614 enfants / 6090 min ».
- Indique le lieu : « Campus Professeur Zacus, laboratoire principal ».
> Astuce : laisse un espace pour coller un badge ou un sticker.
@@ -0,0 +1,6 @@
# Prompt : invitation verso
- Format : A6, verso. Crée une grille simple avec les horaires de la mission (accueil, exploration, synthèse).
- Mentionne les règles anti-chaos : par exemple, « un micro à la fois », « respecte les preuves ».
- Ajoute une check-list : « Badge prêt », « Fiche denquête distribuée », « QR audio testé ».
- Laisse un petit encadré « Équipe » pour inscrire les noms des enfants.
+6
View File
@@ -0,0 +1,6 @@
# Prompt : règles en 1 page
- Format : A4, orientation portrait.
- Contenu : 3 blocs (immédiat, exploration, synthèse) avec icônes et temps alignés aux 6090 min.
- Mentionne les règles anti-chaos (micro unique, écoute active, question fermée) et les moments de pause.
- Ajoute une mini-énigme finale qui résume le mobile moral.
+5
View File
@@ -0,0 +1,5 @@
# Prompt : affiches zones A4
- Format : A4, orientation portrait.
- Contient un titre « Zone Zx » (x variable 16), un petit plan stylisé et une liste dindice disponible.
- Ajoute une barre inférieure montrant la station associée (ex. Atelier des ondes).
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Validate printable manifest YAML files against expected prompts."""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
try:
import yaml
except ImportError: # pragma: no cover
print("Missing dependency: install PyYAML (pip install pyyaml) to validate printables manifests.")
sys.exit(1)
BASE_DIR = Path("printables")
REQUIRED_MANIFEST_KEYS = ["manifest_id", "version", "scenario_id", "items"]
def load_manifest(path: Path) -> dict:
try:
return yaml.safe_load(path.read_text())
except yaml.YAMLError as exc:
raise ValueError(f"Invalid YAML in {path}: {exc}")
def validate_manifest(path: Path) -> list[str]:
manifest = load_manifest(path)
missing = [key for key in REQUIRED_MANIFEST_KEYS if key not in manifest]
if missing:
raise ValueError(f"Manifest missing keys: {', '.join(missing)}")
items = manifest.get("items")
if not isinstance(items, list) or not items:
raise ValueError("`items` must be a non-empty list")
errors: list[str] = []
for entry in items:
if not isinstance(entry, dict):
errors.append("manifest item must be a mapping")
continue
if "id" not in entry:
errors.append("manifest item missing `id`")
continue
if "prompt" not in entry:
errors.append(f"item {entry.get('id', '<unknown>')} missing `prompt`")
continue
prompt_path = BASE_DIR / entry["prompt"]
if not prompt_path.exists():
errors.append(f"missing prompt file for {entry['id']}: {prompt_path}")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description="Validate printables manifest YAML files.")
parser.add_argument("manifest", nargs="?", default="printables/manifests/zacus_v1_printables.yaml")
args = parser.parse_args()
manifest_path = Path(args.manifest)
if not manifest_path.exists():
print(f"Manifest file not found: {manifest_path}")
return 1
try:
errors = validate_manifest(manifest_path)
except ValueError as exc:
print(f"[printables-validate] error: {exc}")
return 1
if errors:
for error in errors:
print(f"[printables-validate] missing: {error}")
return 1
print(f"[printables-validate] OK {manifest_path.name} items={len(load_manifest(manifest_path)['items'])}")
return 0
if __name__ == "__main__":
sys.exit(main())
+231
View File
@@ -0,0 +1,231 @@
#!/usr/bin/env python3
"""Export Zacus scenarios into simple Markdown briefs for the kit and docs."""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
try:
import yaml
except ImportError: # pragma: no cover
print("Missing dependency: install PyYAML (pip install pyyaml) to export scenarios.")
sys.exit(1)
KIT_DIR = Path("kit-maitre-du-jeu/_generated")
DOCS_DIR = Path("docs/_generated")
def normalize(value: str | None) -> str:
if value is None:
return ""
return " ".join(str(value).strip().split())
def section(title: str, lines: list[str]) -> list[str]:
output = [f"## {title}"] if title else []
if title and lines:
output.append("")
output.extend(lines)
output.append("")
return output
def build_script_doc(scenario: dict) -> str:
title = scenario.get("title") or scenario.get("id")
canon = scenario.get("canon", {})
timeline = canon.get("timeline", [])
stations = scenario.get("stations", [])
puzzles = scenario.get("puzzles", [])
solution = scenario.get("solution", {})
lines = [f"# Script — {title}", ""]
lines += section("Introduction", [normalize(canon.get("introduction")) or "(à compléter)"])
if timeline:
lines += section("Chronologie canon", [f"- **{entry.get('label', 'Moment')}** — {normalize(entry.get('note'))}" for entry in timeline])
else:
lines += section("Chronologie canon", ["- À compléter dans le YAML."])
if stations:
lines += section("Stations", [f"- **{station.get('name', 'Station')}** — {normalize(station.get('focus'))}" for station in stations])
else:
lines += section("Stations", ["- Pas de stations définies."])
if puzzles:
lines += section("Puzzles", [f"- {puzzle.get('id')} ({puzzle.get('type')}) — {normalize(puzzle.get('clue'))}" for puzzle in puzzles])
else:
lines += section("Puzzles", ["- Aucun puzzle renseigné."])
solution_lines = [f"- Coupable : {solution.get('culprit', 'à définir')}", f"- Mobile : {normalize(solution.get('motive'))}", f"- Méthode : {normalize(solution.get('method'))}"]
proof = solution.get("proof") or []
if proof:
solution_lines.append("- Preuves :")
solution_lines += [f" - {normalize(item)}" for item in proof]
else:
solution_lines.append("- Preuves : à compléter (au moins 3).")
lines += section("Solution canon", solution_lines)
lines += section("Notes pratiques", [f"- Solution unique : {scenario.get('solution_unique')}"],)
return "\n".join(lines).strip() + "\n"
def build_checklist_doc(scenario: dict, scenario_path: Path) -> str:
players = scenario.get("players", {})
duration = scenario.get("duration_minutes", {})
stations = scenario.get("stations", [])
puzzles = scenario.get("puzzles", [])
solution = scenario.get("solution", {})
proof = solution.get("proof") or []
lines = ["# Checklist scénario", ""]
lines += section("Configurer", [
f"- Scénario : {scenario.get('title') or scenario.get('id')}",
f"- Fichier source : {scenario_path}",
f"- Participants : {players.get('min', '?')}{players.get('max', '?')}",
f"- Durée : {duration.get('min', '?')}{duration.get('max', '?')} min",
f"- Stations prévues : {len(stations)}",
f"- Puzzles : {len(puzzles)}",
f"- Preuves listées : {len(proof)}",
])
lines += section("Matériel recommandé", [
"- Enveloppes numérotées",
"- Badges détective + fiches denquête",
"- Audio : audio/manifests/zacus_v1_audio.yaml",
"- Printables : suivre printables/manifests/zacus_v1_printables.yaml",
])
lines += section("Vérifications", [
"- python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml",
"- python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml",
])
return "\n".join(lines).strip() + "\n"
def build_anti_chaos_doc(scenario: dict) -> str:
players = scenario.get("players", {})
duration = scenario.get("duration_minutes", {})
lines = ["# Guide anti-chaos généré", ""]
lines += section("Règles", [
f"- Max {players.get('max', '?')} enfants : rotation toutes les 15 min.",
f"- Durée cible {duration.get('min', '?')}{duration.get('max', '?')} min : deux pauses courtes.",
"- Un seul enfant parle à la fois, signal sonore pour les transitions.",
"- Indices dans leurs enveloppes jusqu’à validation du chef de piste.",
])
lines += section("Interventions", [
"- Propose un mini-défi express si ça déraille.",
"- Donne un rôle dobservateur aux enfants en retrait.",
])
return "\n".join(lines).strip() + "\n"
def build_roles_doc(scenario: dict) -> str:
players = scenario.get("players", {})
lines = ["# Répartition des rôles", ""]
lines += section("Rôles principaux", [
"- Chef de piste : centralise les indices et relit la timeline.",
"- Chronométreur : marque les phases (exploration, synthèse, accusation).",
"- Analyste technique : gère les codes et les puzzles logiques.",
"- Archiviste : organise les preuves (empreintes, logs, mobile).",
"- Témoin narrateur : redonne les consignes anti-chaos et raconte.",
"- Gardien des objets : protège les enveloppes, badges et fiches.",
])
lines += section("Rôles optionnels", [
"- Détective des émotions : remet le calme si besoin.",
"- Cartographe : trace le parcours des stations.",
"- Ambassadeur : valide que rien na été oublié.",
"- Journaliste : consigne la conclusion finale.",
])
return "\n".join(lines).strip() + "\n"
def build_stations_doc(scenario: dict) -> str:
stations = scenario.get("stations", [])
puzzles = scenario.get("puzzles", [])
lines = ["# Stations détaillées", ""]
if stations:
for station in stations:
lines += [f"## {station.get('name', 'Station')}", "", f"- Focus : {normalize(station.get('focus'))}", f"- Clue : {normalize(station.get('clue'))}", ""]
else:
lines += ["- Aucune station définie dans le scénario."]
if puzzles:
lines += ["## Puzzles", ""]
for puzzle in puzzles:
lines += [f"- {puzzle.get('id')} ({puzzle.get('type')}) : {normalize(puzzle.get('clue'))}", f" Effet : {normalize(puzzle.get('effect'))}", ""]
return "\n".join(lines).strip() + "\n"
def build_brief_doc(scenario: dict) -> str:
title = scenario.get("title") or scenario.get("id")
players = scenario.get("players", {})
duration = scenario.get("duration_minutes", {})
canon = scenario.get("canon", {})
timeline = canon.get("timeline", [])
stations = scenario.get("stations", [])
puzzles = scenario.get("puzzles", [])
solution = scenario.get("solution", {})
lines = [f"# SCENARIO BRIEF — {title}", ""]
lines += section("Stats", [
f"- Joueurs : {players.get('min', '?')}{players.get('max', '?')}",
f"- Durée : {duration.get('min', '?')}{duration.get('max', '?')} min",
f"- Solution unique : {scenario.get('solution_unique')}",
])
lines += section("Introduction", [normalize(canon.get('introduction')) or "(à compléter)"])
if timeline:
lines += section("Chronologie", [f"- **{entry.get('label')}** — {normalize(entry.get('note'))}" for entry in timeline])
else:
lines += section("Chronologie", ["- À compléter."])
if stations:
lines += section("Stations", [f"- **{station.get('name')}** — {normalize(station.get('focus'))} | Indice : {normalize(station.get('clue'))}" for station in stations])
else:
lines += section("Stations", ["- À compléter."])
if puzzles:
lines += section("Puzzles", [f"- {puzzle.get('id')} ({puzzle.get('type')}) — {normalize(puzzle.get('clue'))}\n Effet : {normalize(puzzle.get('effect'))}" for puzzle in puzzles])
else:
lines += section("Puzzles", ["- À compléter."])
solution_lines = [f"- Coupable : {solution.get('culprit', 'à définir')}", f"- Mobile : {normalize(solution.get('motive'))}", f"- Méthode : {normalize(solution.get('method'))}"]
proof = solution.get("proof") or []
if proof:
solution_lines += ["- Preuves:"] + [f" - {normalize(item)}" for item in proof]
lines += section("Solution", solution_lines)
notes = scenario.get("notes")
if notes:
lines += section("Notes", [normalize(notes)])
return "\n".join(lines).strip() + "\n"
def write_file(path: Path, content: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
def export_files(scenario_path: Path) -> None:
if not scenario_path.exists():
print(f"Scenario file not found: {scenario_path}")
sys.exit(1)
scenario = yaml.safe_load(scenario_path.read_text())
files = {
KIT_DIR / "script.md": build_script_doc(scenario),
KIT_DIR / "checklist.md": build_checklist_doc(scenario, scenario_path),
KIT_DIR / "anti-chaos.md": build_anti_chaos_doc(scenario),
KIT_DIR / "roles.md": build_roles_doc(scenario),
KIT_DIR / "stations.md": build_stations_doc(scenario),
DOCS_DIR / "SCENARIO_BRIEF.md": build_brief_doc(scenario),
}
for path, content in files.items():
write_file(path, content)
print("[export] generated Markdown briefs for", scenario_path.name)
def main() -> int:
parser = argparse.ArgumentParser(description="Export Zacus scenario briefs as Markdown.")
parser.add_argument("scenario", type=Path, help="Path to the scenario YAML file")
args = parser.parse_args()
export_files(args.scenario)
return 0
if __name__ == "__main__":
sys.exit(main())