chore: coherence pass (exports, manifests, validators, docs, licenses, CI)
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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 à l’usage pour 6–14 enfants pendant 60–90 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
|
||||
|
||||
|
||||
@@ -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
@@ -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 qu’un 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 d’enquê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
@@ -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, 60–90 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 l’accueil 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.
|
||||
|
||||
@@ -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 6–14 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 l’audio** 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 s’assurer que chaque ID a un prompt accessible.
|
||||
@@ -0,0 +1,49 @@
|
||||
# SCENARIO BRIEF — Le mystère du Professeur Zacus
|
||||
|
||||
## Stats
|
||||
|
||||
- Joueurs : 6–14
|
||||
- Durée : 60–90 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 : l’empreinte 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 l’expé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 l’emplacement 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 s’est ouverte manuellement.
|
||||
- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
|
||||
Effet : Donne le mobile moral unique et pousse vers l’accusation finale.
|
||||
|
||||
## Solution
|
||||
|
||||
- Coupable : Lila Caron, l’assistante 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 qu’elle connaissait mieux que quiconque. Elle craignait que la direction confie l’Auré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:
|
||||
- L’empreinte de gants est celle qu’elle 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 l’Auréole était essentielle.
|
||||
@@ -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 60–90 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 d’observateur aux enfants en retrait.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Checklist scénario
|
||||
|
||||
## Configurer
|
||||
|
||||
- Scénario : Le mystère du Professeur Zacus
|
||||
- Fichier source : game/scenarios/zacus_v1.yaml
|
||||
- Participants : 6–14
|
||||
- Durée : 60–90 min
|
||||
- Stations prévues : 4
|
||||
- Puzzles : 4
|
||||
- Preuves listées : 3
|
||||
|
||||
## Matériel recommandé
|
||||
|
||||
- Enveloppes numérotées
|
||||
- Badges détective + fiches d’enquê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
|
||||
@@ -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 n’a été oublié.
|
||||
- Journaliste : consigne la conclusion finale.
|
||||
@@ -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 l’expé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 l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
|
||||
|
||||
## Solution canon
|
||||
|
||||
- Coupable : Lila Caron, l’assistante 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 qu’elle connaissait mieux que quiconque. Elle craignait que la direction confie l’Auré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 :
|
||||
- L’empreinte de gants est celle qu’elle 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
|
||||
@@ -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 : l’empreinte 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 l’expé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 l’emplacement 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 s’est ouverte manuellement.
|
||||
|
||||
- mobile (narration) : Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
|
||||
Effet : Donne le mobile moral unique et pousse vers l’accusation 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.
|
||||
@@ -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.
|
||||
@@ -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 l’enquête (ex. « Déclenche l’audio 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 l’Auré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.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Prompt : fiche d’enquê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.
|
||||
@@ -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 l’enregistrement 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 « 6–14 enfants / 60–90 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 d’enquête distribuée », « QR audio testé ».
|
||||
- Laisse un petit encadré « Équipe » pour inscrire les noms des enfants.
|
||||
@@ -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 60–90 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.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Prompt : affiches zones A4
|
||||
|
||||
- Format : A4, orientation portrait.
|
||||
- Contient un titre « Zone Zx » (x variable 1–6), un petit plan stylisé et une liste d’indice disponible.
|
||||
- Ajoute une barre inférieure montrant la station associée (ex. Atelier des ondes).
|
||||
Executable
+80
@@ -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())
|
||||
Executable
+231
@@ -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 d’enquê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 d’observateur 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 n’a é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())
|
||||
Reference in New Issue
Block a user