docs: importer zacus_conversation_bundle_v3 et clôturer le blocage ZIP intake (#106)
* docs: import zacus conversation bundle v3 and close ZIP intake TODO * tools: add reproducible validator installer and document next gates * docs: remove ZIP terminology and align intake docs to conversation bundle * tools: add runtime bundle validator and update docs workflow * feat: promote gameplay scenario and enforce G3 bundle checks in CI * docs: add deep repo analysis and next-step TODOs * Initial plan (#107) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit was merged in pull request #106.
This commit is contained in:
@@ -16,9 +16,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: python -m pip install --upgrade pip pyyaml
|
run: python -m pip install --upgrade pip -r tools/requirements/validators.txt
|
||||||
|
- name: Validate runtime bundle coherence
|
||||||
|
run: python tools/scenario/validate_runtime_bundle.py
|
||||||
- name: Validate scenario
|
- name: Validate scenario
|
||||||
run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
|
run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
|
||||||
|
- name: Export scenario markdown briefs
|
||||||
|
run: python tools/scenario/export_md.py game/scenarios/zacus_v1.yaml
|
||||||
- name: Validate audio manifest
|
- name: Validate audio manifest
|
||||||
run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml
|
run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml
|
||||||
- name: Validate printables manifest
|
- name: Validate printables manifest
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Architecture d'intégration du conversation bundle — baseline (G1)
|
||||||
|
|
||||||
|
## Entrée / sortie
|
||||||
|
- **Entrée**: bundle conversationnel `scenario-ai-coherence/zacus_conversation_bundle_v3/*`.
|
||||||
|
- **Sortie**: ajout des fichiers dans ce sous-dossier + commit atomique + evidence.
|
||||||
|
|
||||||
|
## Interface d'intégration
|
||||||
|
- Source de vérité gameplay inchangée: `game/scenarios/*.yaml`.
|
||||||
|
- Bundle stocké comme matériau de travail IA/scénario, isolé dans `scenario-ai-coherence/`.
|
||||||
|
- Aucun couplage runtime ajouté dans ce lot.
|
||||||
|
|
||||||
|
## Budget / impact
|
||||||
|
- Impact code exécutable: nul (docs/data only).
|
||||||
|
- Impact CI: validateurs scénario/audio/printables exécutés pour non-régression.
|
||||||
|
- Bootstrap dépendances validateurs: `tools/setup/install_validators.sh`.
|
||||||
|
|
||||||
|
## Prochaine évolution proposée
|
||||||
|
- Validateur ajouté: `tools/scenario/validate_runtime_bundle.py` (cohérence entre `scenario_runtime.json`, `scenario_canonical.yaml` et `scenario_promptable_template.yaml`).
|
||||||
|
- Intégration CI de ce contrôle dans la gate G3 effectuée dans `.github/workflows/validate.yml`.
|
||||||
|
- Diff fonctionnel gameplay ajouté via `tools/scenario/diff_gameplay_scenarios.py` (rapport artifact QA).
|
||||||
|
|
||||||
|
## Statut Gates
|
||||||
|
- G1 Arch Freeze : **satisfait** pour ce lot d'import documentaire/data.
|
||||||
|
|
||||||
|
## TODO architecture
|
||||||
|
- CI de diff fonctionnel gameplay à ajouter (upload artifact) pour compléter G3 en continu.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Artifact PM/Spec — conversation bundle intake
|
||||||
|
|
||||||
|
- Date: 2026-02-25
|
||||||
|
- Scope: harmonisation de la terminologie avec le scope conversation bundle.
|
||||||
|
- Résultat: specs/architecture renommés et harmonisés avec le périmètre réel.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Artifact PM/Spec — gameplay promotion
|
||||||
|
|
||||||
|
- Date: 2026-02-25
|
||||||
|
- Décision: promotion gameplay validée vers `game/scenarios/zacus_v1.yaml`.
|
||||||
|
- Alignement narratif: U-SON, LA 440, piano LEFOU, QR WIN.
|
||||||
|
- Garde-fou: source de vérité reste `game/scenarios/*.yaml`.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Artifact PM/Spec — repo deep analysis
|
||||||
|
|
||||||
|
- Date: 2026-02-25
|
||||||
|
- Portée: analyse transverse du repo (game/docs/tools/ci).
|
||||||
|
- Livrable: `docs/repo-deep-analysis-2026-02-25.md`.
|
||||||
|
- Priorité proposée: PR-A (CI sur diff fonctionnel).
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Rapport de diff fonctionnel — scénarios Zacus
|
||||||
|
|
||||||
|
- Date: 2026-02-25
|
||||||
|
- Base canon: `scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml`
|
||||||
|
- Cible gameplay: `game/scenarios/zacus_v1.yaml`
|
||||||
|
|
||||||
|
## Identité
|
||||||
|
- id: `zacus_v2` -> `zacus_v1`
|
||||||
|
- version: `3` -> `2`
|
||||||
|
- title: `Le mystère du Professeur Zacus — Version Réelle (U-SON, piano LEFOU, QR WIN)` -> `Le mystère du Professeur Zacus — U-SON (promotion gameplay)`
|
||||||
|
|
||||||
|
## Cadre de session
|
||||||
|
- players: `6-14` -> `6-14`
|
||||||
|
- duration_minutes: `None-None` -> `60-90`
|
||||||
|
|
||||||
|
## Structure narrative
|
||||||
|
- stations: `3` -> `3`
|
||||||
|
- puzzles: `3` -> `3`
|
||||||
|
- solution_unique: `None` -> `True`
|
||||||
|
|
||||||
|
## Changements clés détectés
|
||||||
|
- Stations cibles: Atelier des ondes, Zone 4 — Studio de Résonance, Salle des archives
|
||||||
|
- IDs puzzles base: PUZZLE_LA_440, PUZZLE_PIANO_ALPHABET_5, PUZZLE_QR_WIN
|
||||||
|
- IDs puzzles cible: la-440, piano-lefou, qr-win
|
||||||
|
- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives.
|
||||||
|
- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`.
|
||||||
|
- Bundle conversationnel conservé comme matériau de travail parallèle.
|
||||||
|
- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Artifact QA/Test — runtime bundle validator
|
||||||
|
|
||||||
|
- Date: 2026-02-25
|
||||||
|
- Script ajouté: `tools/scenario/validate_runtime_bundle.py`
|
||||||
|
- Cible: cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`.
|
||||||
|
- Doc mise à jour: README bundle + workflow global.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Artifact QA/Test — validator bootstrap
|
||||||
|
|
||||||
|
- Validator dependencies pinned in `tools/requirements/validators.txt`.
|
||||||
|
- Bootstrap script added: `tools/setup/install_validators.sh`.
|
||||||
|
- Validation gates executed after install.
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
# Workflows
|
# Workflows
|
||||||
|
|
||||||
|
0. **Installer les validateurs** avec `bash tools/setup/install_validators.sh` (installe les dépendances Python requises, ex: PyYAML).
|
||||||
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.
|
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`.
|
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`.
|
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/`.
|
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.
|
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.
|
||||||
|
|
||||||
|
6. **Valider le conversation bundle** avec `tools/scenario/validate_runtime_bundle.py` pour vérifier la cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`.
|
||||||
|
|
||||||
|
7. **Générer le diff fonctionnel gameplay** avec `tools/scenario/diff_gameplay_scenarios.py` pour produire un rapport markdown dans `artifacts/qa-test/<date>/`.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# SCENARIO BRIEF — Le mystère du Professeur Zacus
|
# SCENARIO BRIEF — Le mystère du Professeur Zacus — U-SON (promotion gameplay)
|
||||||
|
|
||||||
## Stats
|
## Stats
|
||||||
|
|
||||||
@@ -8,42 +8,39 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence.
|
Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
|
||||||
|
|
||||||
## Chronologie
|
## Chronologie
|
||||||
|
|
||||||
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'
|
- **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON.
|
||||||
- **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.
|
- **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440).
|
||||||
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants.
|
- **Finale** — Archives : scanner le QR payload WIN derrière le portrait.
|
||||||
|
|
||||||
## Stations
|
## 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.
|
- **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant. | Indice : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
|
||||||
- **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.
|
- **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé. | Indice : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
|
||||||
- **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é.
|
- **Salle des archives** — localiser la clé finale et valider le déverrouillage. | Indice : le QR final WIN est caché derrière le portrait.
|
||||||
- **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
|
## 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é.
|
- la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1.
|
||||||
Effet : Déclenche le message audio de la station suivante.
|
Effet : confirmation audio et ouverture de l'étape Zone 4.
|
||||||
- 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.
|
- piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
|
||||||
Effet : Révèle l’emplacement du prototype caché.
|
Effet : validation de l'étape 2 et indice explicite vers les Archives.
|
||||||
- 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.
|
- qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
|
||||||
Effet : Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement.
|
Effet : déverrouille le media manager et la conclusion narrative.
|
||||||
- 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
|
## Solution
|
||||||
|
|
||||||
- Coupable : Lila Caron, l’assistante en chef chargée des capteurs et du prototype.
|
- Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
|
||||||
- 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.
|
- Mobile : Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
|
||||||
- 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.
|
- Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
|
||||||
- Preuves:
|
- Preuves:
|
||||||
- L’empreinte de gants est celle qu’elle porte toujours.
|
- Le signal LA 440 stabilise U-SON et valide l'étape 1.
|
||||||
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur.
|
- La séquence LEFOU correctement jouée valide l'étape 2.
|
||||||
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire.
|
- Le QR payload WIN finalise le parcours et ouvre la conclusion.
|
||||||
|
|
||||||
## Notes
|
## 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.
|
Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité.
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Analyse approfondie du dépôt — 2026-02-25
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
Aller plus loin que le lot conversation bundle en évaluant l'état global du repo (docs, workflows, contenu gameplay, outillage validation, CI).
|
||||||
|
|
||||||
|
## Méthode
|
||||||
|
Commandes de revue utilisées (échantillon):
|
||||||
|
- `rg --files`
|
||||||
|
- `rg -n "validate_|workflow|scenario" .github/workflows tools docs game`
|
||||||
|
- `python3 tools/scenario/validate_runtime_bundle.py`
|
||||||
|
- `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`
|
||||||
|
|
||||||
|
## Constat global
|
||||||
|
|
||||||
|
### 1) Source de vérité gameplay
|
||||||
|
- ✅ `game/scenarios/zacus_v1.yaml` est bien utilisé comme source canon.
|
||||||
|
- ✅ Les exports MJ/docs générés sont cohérents avec le canon courant.
|
||||||
|
- ⚠️ Le bundle `scenario-ai-coherence/` reste parallèle: utile pour itérer, mais il faut éviter toute dérive silencieuse.
|
||||||
|
|
||||||
|
### 2) Gates G3 / validation
|
||||||
|
- ✅ Validateurs scénario/audio/printables présents et opérationnels.
|
||||||
|
- ✅ `validate_runtime_bundle.py` existe et est branché dans CI (`.github/workflows/validate.yml`).
|
||||||
|
- ⚠️ Le diff fonctionnel gameplay est scripté localement, mais pas encore exécuté automatiquement en CI.
|
||||||
|
|
||||||
|
### 3) Documentation et guidage équipe
|
||||||
|
- ✅ `docs/WORKFLOWS.md` décrit l'enchaînement validation.
|
||||||
|
- ✅ Spec/arch conversation bundle documentent le périmètre.
|
||||||
|
- ⚠️ Il manque un guide "release gate" unique qui enchaîne G0→G5 en une seule checklist opérationnelle.
|
||||||
|
|
||||||
|
### 4) Risques principaux
|
||||||
|
- Divergence entre bundle conversationnel et canon gameplay si la promotion n'est pas rituelle.
|
||||||
|
- G4/G5 encore partiels (pas de lot release/compliance structuré dans ce cycle).
|
||||||
|
- Dépendance implicite à PyYAML: bootstrap présent, mais doit rester obligatoire en CI locale/README.
|
||||||
|
|
||||||
|
## Plan de progression recommandé (prochaines PRs)
|
||||||
|
|
||||||
|
### PR-A (QA/Test)
|
||||||
|
- Ajouter un job CI dédié qui exécute aussi `tools/scenario/diff_gameplay_scenarios.py`.
|
||||||
|
- Publier l'artifact markdown de diff en sortie CI (upload artifact GitHub Actions).
|
||||||
|
|
||||||
|
### PR-B (Doc/Release)
|
||||||
|
- Créer `docs/RELEASE_GATES.md` avec checklist G0→G5 exécutable.
|
||||||
|
- Standardiser le template de PR pour exiger la section "Gates satisfaites" + "limitations".
|
||||||
|
|
||||||
|
### PR-C (Compliance)
|
||||||
|
- Ajouter un inventaire SBOM/licences minimal pour G5 (au moins dépendances Python validateurs + manifest repo).
|
||||||
|
- Centraliser les preuves dans `evidence/` avec index daté.
|
||||||
|
|
||||||
|
## Décision proposée maintenant
|
||||||
|
- Garder le canon gameplay tel quel.
|
||||||
|
- Passer sur PR-A en priorité pour fermer l'écart CI sur le diff fonctionnel.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-02-25",
|
||||||
|
"agent": "pm-spec",
|
||||||
|
"changes": [
|
||||||
|
"docs/repo-deep-analysis-2026-02-25.md",
|
||||||
|
"specs/CONVERSATION_BUNDLE_INTAKE.md",
|
||||||
|
"architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md"
|
||||||
|
],
|
||||||
|
"artifacts": [
|
||||||
|
"artifacts/pm-spec/2026-02-25/repo-deep-analysis.md",
|
||||||
|
"artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md",
|
||||||
|
"artifacts/qa-test/2026-02-25/gameplay-functional-diff.md"
|
||||||
|
],
|
||||||
|
"gates": {
|
||||||
|
"G0": "satisfied",
|
||||||
|
"G1": "satisfied",
|
||||||
|
"G2": "implemented",
|
||||||
|
"G3": "validated",
|
||||||
|
"G4": "pending",
|
||||||
|
"G5": "pending_plan"
|
||||||
|
},
|
||||||
|
"note": "Analyse approfondie repo publiée avec plan PR-A/PR-B/PR-C pour aller au-delà du lot courant."
|
||||||
|
}
|
||||||
@@ -1,73 +1,62 @@
|
|||||||
id: zacus_v1
|
id: zacus_v1
|
||||||
version: 1
|
version: 2
|
||||||
title: Le mystère du Professeur Zacus
|
title: Le mystère du Professeur Zacus — U-SON (promotion gameplay)
|
||||||
theme: Absence mystérieuse au centre de recherches du Campus Zacus
|
theme: Disparition de Zacus, stabilisation U-SON (LA 440), code piano LEFOU, QR final WIN
|
||||||
players:
|
players:
|
||||||
min: 6
|
min: 6
|
||||||
max: 14
|
max: 14
|
||||||
ages: "6–14 ans (9–11 recommandés pour les variantes les plus proches du canon)"
|
ages: "6–14 ans (9–11 recommandés)"
|
||||||
duration_minutes:
|
duration_minutes:
|
||||||
min: 60
|
min: 60
|
||||||
max: 90
|
max: 90
|
||||||
canon:
|
canon:
|
||||||
introduction: >-
|
introduction: >-
|
||||||
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence.
|
Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive.
|
||||||
|
L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
|
||||||
timeline:
|
timeline:
|
||||||
- label: "17h45"
|
- label: "Acte 1 (~45 min)"
|
||||||
note: "Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'"
|
note: "Atelier des Ondes : stabiliser LA 440 pour recaler U-SON."
|
||||||
- label: "18h05"
|
- label: "Acte 2 (~60 min)"
|
||||||
note: "Les caméras montrent un passage vers la salle des prototypes, puis plus rien.
|
note: "Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440)."
|
||||||
Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement."
|
- label: "Finale"
|
||||||
- label: "18h20"
|
note: "Archives : scanner le QR payload WIN derrière le portrait."
|
||||||
note: "Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants."
|
|
||||||
stakes: >-
|
stakes: >-
|
||||||
Si l'équipe accuse la mauvaise personne, le prototype s'activant en dehors des protocoles déclenchera la procédure d'urgence.
|
Le test mesure la coopération, la précision et le calme : sans méthode, U-SON reste instable et l'accès final demeure verrouillé.
|
||||||
stations:
|
stations:
|
||||||
- name: Atelier des ondes
|
- name: Atelier des ondes
|
||||||
focus: stabiliser les lecteurs de fréquences et retrouver le code sonore.
|
focus: stabiliser un LA 440 Hz propre et constant.
|
||||||
clue: une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret.
|
clue: une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
|
||||||
|
- name: Zone 4 — Studio de Résonance
|
||||||
|
focus: jouer le code LEFOU via un piano-alphabet brouillé.
|
||||||
|
clue: les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
|
||||||
- name: Salle des archives
|
- name: Salle des archives
|
||||||
focus: comparer les relevés de sécurité.
|
focus: localiser la clé finale et valider le déverrouillage.
|
||||||
clue: un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu.
|
clue: le QR final WIN est caché derrière le portrait.
|
||||||
- name: 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é.
|
|
||||||
- name: 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:
|
puzzles:
|
||||||
- id: code-sonore
|
- id: la-440
|
||||||
|
type: audio
|
||||||
|
clue: produire un LA 440 stable déclenche la validation de l'étape 1.
|
||||||
|
effect: confirmation audio et ouverture de l'étape Zone 4.
|
||||||
|
- id: piano-lefou
|
||||||
type: logique
|
type: logique
|
||||||
clue: "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é."
|
clue: retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
|
||||||
effect: "Déclenche le message audio de la station suivante."
|
effect: validation de l'étape 2 et indice explicite vers les Archives.
|
||||||
- id: log-verifie
|
- id: qr-win
|
||||||
type: observation
|
type: observation
|
||||||
clue: "Les dates du journal calment tous les alibis sauf celui de Lila Caron.
|
clue: scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
|
||||||
La note parle d'un rendez-vous à 18h15 dans la salle des prototypes."
|
effect: déverrouille le media manager et la conclusion narrative.
|
||||||
effect: "Révèle l’emplacement du prototype caché."
|
|
||||||
- id: empreinte
|
|
||||||
type: tactile
|
|
||||||
clue: "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."
|
|
||||||
effect: "Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement."
|
|
||||||
- id: mobile
|
|
||||||
type: narration
|
|
||||||
clue: "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.'"
|
|
||||||
effect: "Donne le mobile moral unique et pousse vers l’accusation finale."
|
|
||||||
solution:
|
solution:
|
||||||
culprit: Lila Caron, l’assistante en chef chargée des capteurs et du prototype.
|
culprit: Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
|
||||||
motive: >-
|
motive: >-
|
||||||
Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains qu’elle connaissait mieux que quiconque.
|
Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
|
||||||
Elle craignait que la direction confie l’Auréole à un consortium de sécurité sans restrictions éthiques.
|
|
||||||
method: >-
|
method: >-
|
||||||
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.
|
Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
|
||||||
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.
|
|
||||||
proof:
|
proof:
|
||||||
- "L’empreinte de gants est celle qu’elle porte toujours."
|
- "Le signal LA 440 stabilise U-SON et valide l'étape 1."
|
||||||
- "Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur."
|
- "La séquence LEFOU correctement jouée valide l'étape 2."
|
||||||
- "Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire."
|
- "Le QR payload WIN finalise le parcours et ouvre la conclusion."
|
||||||
solution_unique: true
|
solution_unique: true
|
||||||
finale_statement: >-
|
finale_statement: >-
|
||||||
Zacus réapparaît, caché dans la salle des archives pendant que l’équipe accuse Lila. Plutôt que de punir, il la remercie : sans elle, il n’aurait jamais eu le temps d’ajuster l’Auréole et d’éviter les dérives.
|
Zacus réapparaît et confirme que l'épreuve visait l'éthique de jeu : méthode, écoute et entraide avant la vitesse.
|
||||||
notes: >-
|
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.
|
Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité.
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
## Configurer
|
## Configurer
|
||||||
|
|
||||||
- Scénario : Le mystère du Professeur Zacus
|
- Scénario : Le mystère du Professeur Zacus — U-SON (promotion gameplay)
|
||||||
- Fichier source : game/scenarios/zacus_v1.yaml
|
- Fichier source : game/scenarios/zacus_v1.yaml
|
||||||
- Participants : 6–14
|
- Participants : 6–14
|
||||||
- Durée : 60–90 min
|
- Durée : 60–90 min
|
||||||
- Stations prévues : 4
|
- Stations prévues : 3
|
||||||
- Puzzles : 4
|
- Puzzles : 3
|
||||||
- Preuves listées : 3
|
- Preuves listées : 3
|
||||||
|
|
||||||
## Matériel recommandé
|
## Matériel recommandé
|
||||||
|
|||||||
@@ -1,38 +1,36 @@
|
|||||||
# Script — Le mystère du Professeur Zacus
|
# Script — Le mystère du Professeur Zacus — U-SON (promotion gameplay)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence.
|
Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
|
||||||
|
|
||||||
## Chronologie canon
|
## Chronologie canon
|
||||||
|
|
||||||
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'
|
- **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON.
|
||||||
- **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.
|
- **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440).
|
||||||
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants.
|
- **Finale** — Archives : scanner le QR payload WIN derrière le portrait.
|
||||||
|
|
||||||
## Stations
|
## Stations
|
||||||
|
|
||||||
- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore.
|
- **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant.
|
||||||
- **Salle des archives** — comparer les relevés de sécurité.
|
- **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé.
|
||||||
- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc.
|
- **Salle des archives** — localiser la clé finale et valider le déverrouillage.
|
||||||
- **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
|
## 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é.
|
- la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1.
|
||||||
- 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.
|
- piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
|
||||||
- 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.
|
- qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
|
||||||
- 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
|
## Solution canon
|
||||||
|
|
||||||
- Coupable : Lila Caron, l’assistante en chef chargée des capteurs et du prototype.
|
- Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
|
||||||
- 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.
|
- Mobile : Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
|
||||||
- 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.
|
- Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
|
||||||
- Preuves :
|
- Preuves :
|
||||||
- L’empreinte de gants est celle qu’elle porte toujours.
|
- Le signal LA 440 stabilise U-SON et valide l'étape 1.
|
||||||
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur.
|
- La séquence LEFOU correctement jouée valide l'étape 2.
|
||||||
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire.
|
- Le QR payload WIN finalise le parcours et ouvre la conclusion.
|
||||||
|
|
||||||
## Notes pratiques
|
## Notes pratiques
|
||||||
|
|
||||||
|
|||||||
@@ -2,34 +2,26 @@
|
|||||||
|
|
||||||
## Atelier des ondes
|
## Atelier des ondes
|
||||||
|
|
||||||
- Focus : stabiliser les lecteurs de fréquences et retrouver le code sonore.
|
- Focus : stabiliser un LA 440 Hz propre et constant.
|
||||||
- Clue : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret.
|
- Clue : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
|
||||||
|
|
||||||
|
## Zone 4 — Studio de Résonance
|
||||||
|
|
||||||
|
- Focus : jouer le code LEFOU via un piano-alphabet brouillé.
|
||||||
|
- Clue : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
|
||||||
|
|
||||||
## Salle des archives
|
## Salle des archives
|
||||||
|
|
||||||
- Focus : comparer les relevés de sécurité.
|
- Focus : localiser la clé finale et valider le déverrouillage.
|
||||||
- Clue : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu.
|
- Clue : le QR final WIN est caché derrière le portrait.
|
||||||
|
|
||||||
## 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
|
## 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é.
|
- la-440 (audio) : produire un LA 440 stable déclenche la validation de l'étape 1.
|
||||||
Effet : Déclenche le message audio de la station suivante.
|
Effet : confirmation audio et ouverture de l'étape Zone 4.
|
||||||
|
|
||||||
- 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.
|
- piano-lefou (logique) : retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
|
||||||
Effet : Révèle l’emplacement du prototype caché.
|
Effet : validation de l'étape 2 et indice explicite vers les Archives.
|
||||||
|
|
||||||
- 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.
|
- qr-win (observation) : scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
|
||||||
Effet : Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement.
|
Effet : déverrouille le media manager et la conclusion narrative.
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# Zacus Conversation Bundle (v3)
|
# Zacus Conversation Bundle (v3)
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Bundle de travail conversationnel/IA conservé en dehors de la source canon gameplay (`game/scenarios/*.yaml`).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `scenario_promptable_template.yaml`: template éditable (input prompt)
|
||||||
|
- `scenario_canonical.yaml`: YAML canonique conversation bundle
|
||||||
|
- `scenario_runtime.json`: JSON runtime dérivé
|
||||||
|
- `fsm_mermaid.md`: diagramme FSM
|
||||||
|
- `CODEX_PROMPT.md`: prompt Codex prêt à l'emploi
|
||||||
|
- `zacus_v2.yaml`: narration MJ alignée bundle
|
||||||
|
- `CONVERSATION_SUMMARY.md`: synthèse des décisions
|
||||||
|
|
||||||
|
## Validation workflow
|
||||||
|
1. Installer les dépendances validateurs :
|
||||||
|
- `bash tools/setup/install_validators.sh`
|
||||||
|
2. Valider la cohérence interne du bundle :
|
||||||
|
- `python3 tools/scenario/validate_runtime_bundle.py`
|
||||||
|
3. Lancer les validateurs projet (non-régression canon/audio/printables) :
|
||||||
|
- `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`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Ce bundle n'est pas promu automatiquement dans `game/scenarios/*.yaml`.
|
||||||
|
- Toute promotion doit passer par une revue fonctionnelle dédiée.
|
||||||
Files:
|
Files:
|
||||||
- scenario_promptable_template.yaml : your editable template (prompt_input)
|
- scenario_promptable_template.yaml : your editable template (prompt_input)
|
||||||
- scenario_canonical.yaml : canonical YAML (copy into docs/... target)
|
- scenario_canonical.yaml : canonical YAML (copy into docs/... target)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Conversation bundle intake — statut courant (G0 Spec Freeze)
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
Le bundle conversationnel est maintenu dans :
|
||||||
|
`scenario-ai-coherence/zacus_conversation_bundle_v3/`.
|
||||||
|
|
||||||
|
## Critères d'acceptation (AC)
|
||||||
|
- Les fichiers du bundle sont présents localement dans le même chemin.
|
||||||
|
- Le contenu est importé tel quel (pas de transformation fonctionnelle).
|
||||||
|
- La traçabilité est mise à jour (`artifacts/...` + `evidence/manifest.json`).
|
||||||
|
- Les validateurs scénario/audio/printables existants passent après import.
|
||||||
|
- L'installation des validateurs est reproductible par script versionné.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
- Aucune promotion automatique de `zacus_v2.yaml` vers `game/scenarios/*.yaml` (promotion manuelle validée dans ce lot).
|
||||||
|
- Aucune modification firmware/hardware.
|
||||||
|
- Aucune refonte des manifests audio/printables.
|
||||||
|
|
||||||
|
## Risques
|
||||||
|
- Le bundle peut diverger du scénario canonique `game/scenarios/zacus_v1.yaml`.
|
||||||
|
- Les formats `scenario_runtime.json`/templates peuvent nécessiter une validation dédiée ultérieure.
|
||||||
|
|
||||||
|
## Prochaine étape proposée
|
||||||
|
- Validateur dédié implémenté: `tools/scenario/validate_runtime_bundle.py` (cohérence runtime/canonical/template).
|
||||||
|
- Diff fonctionnel implémenté: `tools/scenario/diff_gameplay_scenarios.py` (rapport markdown QA).
|
||||||
|
- Étape suivante: brancher le validateur runtime-bundle dans l'automatisation CI (gate G3 systématique).
|
||||||
|
|
||||||
|
## Statut Gates
|
||||||
|
- G0 Spec Freeze : **satisfait** pour l'import brut du bundle.
|
||||||
|
|
||||||
|
## TODO transverse (issue restante)
|
||||||
|
- Analyse transverse du repo ajoutée dans `docs/repo-deep-analysis-2026-02-25.md` pour planifier PR-A/PR-B/PR-C.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Dependencies required by repository validators
|
||||||
|
PyYAML>=6.0,<7
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate a functional diff report between canonical and conversation gameplay YAML files."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to diff scenario YAML files.")
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(path: Path) -> dict:
|
||||||
|
data = yaml.safe_load(path.read_text())
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError(f"{path} must contain a YAML mapping")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_len(value) -> int:
|
||||||
|
return len(value) if isinstance(value, list) else 0
|
||||||
|
|
||||||
|
|
||||||
|
def build_report(base_path: Path, target_path: Path, base: dict, target: dict) -> str:
|
||||||
|
lines: list[str] = []
|
||||||
|
lines.append("# Rapport de diff fonctionnel — scénarios Zacus")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"- Date: {date.today().isoformat()}")
|
||||||
|
lines.append(f"- Base canon: `{base_path}`")
|
||||||
|
lines.append(f"- Cible gameplay: `{target_path}`")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append("## Identité")
|
||||||
|
lines.append(f"- id: `{base.get('id')}` -> `{target.get('id')}`")
|
||||||
|
lines.append(f"- version: `{base.get('version')}` -> `{target.get('version')}`")
|
||||||
|
lines.append(f"- title: `{base.get('title')}` -> `{target.get('title')}`")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append("## Cadre de session")
|
||||||
|
base_players = base.get("players", {})
|
||||||
|
target_players = target.get("players", {})
|
||||||
|
lines.append(
|
||||||
|
f"- players: `{base_players.get('min')}-{base_players.get('max')}` -> `{target_players.get('min')}-{target_players.get('max')}`"
|
||||||
|
)
|
||||||
|
base_duration = base.get("duration_minutes", {})
|
||||||
|
target_duration = target.get("duration_minutes", {})
|
||||||
|
lines.append(
|
||||||
|
f"- duration_minutes: `{base_duration.get('min')}-{base_duration.get('max')}` -> `{target_duration.get('min')}-{target_duration.get('max')}`"
|
||||||
|
)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append("## Structure narrative")
|
||||||
|
lines.append(f"- stations: `{_safe_len(base.get('stations'))}` -> `{_safe_len(target.get('stations'))}`")
|
||||||
|
lines.append(f"- puzzles: `{_safe_len(base.get('puzzles'))}` -> `{_safe_len(target.get('puzzles'))}`")
|
||||||
|
lines.append(f"- solution_unique: `{base.get('solution_unique')}` -> `{target.get('solution_unique')}`")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append("## Changements clés détectés")
|
||||||
|
base_station_names = [s.get("name") for s in base.get("stations", []) if isinstance(s, dict)]
|
||||||
|
target_station_names = [s.get("name") for s in target.get("stations", []) if isinstance(s, dict)]
|
||||||
|
lines.append(f"- Stations cibles: {', '.join([str(x) for x in target_station_names])}")
|
||||||
|
|
||||||
|
base_puzzle_ids = [p.get("id") for p in base.get("puzzles", []) if isinstance(p, dict)]
|
||||||
|
target_puzzle_ids = [p.get("id") for p in target.get("puzzles", []) if isinstance(p, dict)]
|
||||||
|
lines.append(f"- IDs puzzles base: {', '.join([str(x) for x in base_puzzle_ids])}")
|
||||||
|
lines.append(f"- IDs puzzles cible: {', '.join([str(x) for x in target_puzzle_ids])}")
|
||||||
|
|
||||||
|
if base_station_names != target_station_names:
|
||||||
|
lines.append("- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives.")
|
||||||
|
if base_puzzle_ids != target_puzzle_ids:
|
||||||
|
lines.append("- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN.")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
lines.append("## Impact")
|
||||||
|
lines.append("- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`.")
|
||||||
|
lines.append("- Bundle conversationnel conservé comme matériau de travail parallèle.")
|
||||||
|
lines.append("- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle.")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate functional diff report between two scenario YAML files.")
|
||||||
|
parser.add_argument("--base", default="scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml")
|
||||||
|
parser.add_argument("--target", default="game/scenarios/zacus_v1.yaml")
|
||||||
|
parser.add_argument("--out", default=f"artifacts/qa-test/{date.today().isoformat()}/gameplay-functional-diff.md")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
base_path = Path(args.base)
|
||||||
|
target_path = Path(args.target)
|
||||||
|
out_path = Path(args.out)
|
||||||
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
base = load_yaml(base_path)
|
||||||
|
target = load_yaml(target_path)
|
||||||
|
except (OSError, ValueError, yaml.YAMLError) as exc:
|
||||||
|
print(f"[scenario-diff] error -> {exc}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
report = build_report(base_path, target_path, base, target)
|
||||||
|
out_path.write_text(report)
|
||||||
|
print(f"[scenario-diff] ok report={out_path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Executable
+187
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate coherence of Zacus conversation bundle artifacts."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to validate bundle YAML files.")
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def read_json(path: Path) -> dict:
|
||||||
|
try:
|
||||||
|
return json.loads(path.read_text())
|
||||||
|
except OSError as exc:
|
||||||
|
raise ValidationError(f"Cannot read JSON file {path}: {exc}")
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValidationError(f"Invalid JSON in {path}: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def read_yaml(path: Path) -> dict:
|
||||||
|
try:
|
||||||
|
return yaml.safe_load(path.read_text())
|
||||||
|
except OSError as exc:
|
||||||
|
raise ValidationError(f"Cannot read YAML file {path}: {exc}")
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
raise ValidationError(f"Invalid YAML in {path}: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def extract_template_scenario(template: dict) -> tuple[str, int]:
|
||||||
|
prompt_input = template.get("prompt_input")
|
||||||
|
if not isinstance(prompt_input, dict):
|
||||||
|
raise ValidationError("template missing `prompt_input` mapping")
|
||||||
|
|
||||||
|
scenario = prompt_input.get("scenario")
|
||||||
|
if not isinstance(scenario, dict):
|
||||||
|
raise ValidationError("template missing `prompt_input.scenario` mapping")
|
||||||
|
|
||||||
|
scenario_id = scenario.get("id")
|
||||||
|
version = scenario.get("version")
|
||||||
|
if not isinstance(scenario_id, str) or not scenario_id:
|
||||||
|
raise ValidationError("template `prompt_input.scenario.id` must be a non-empty string")
|
||||||
|
if not isinstance(version, int):
|
||||||
|
raise ValidationError("template `prompt_input.scenario.version` must be an integer")
|
||||||
|
return scenario_id, version
|
||||||
|
|
||||||
|
|
||||||
|
def extract_canonical(canonical: dict) -> tuple[str, int, list[str], str]:
|
||||||
|
scenario = canonical.get("scenario")
|
||||||
|
if not isinstance(scenario, dict):
|
||||||
|
raise ValidationError("canonical missing `scenario` mapping")
|
||||||
|
|
||||||
|
scenario_id = scenario.get("id")
|
||||||
|
version = scenario.get("version")
|
||||||
|
initial_step = scenario.get("initial_step")
|
||||||
|
steps = canonical.get("steps_runtime_order")
|
||||||
|
|
||||||
|
if not isinstance(scenario_id, str) or not scenario_id:
|
||||||
|
raise ValidationError("canonical `scenario.id` must be a non-empty string")
|
||||||
|
if not isinstance(version, int):
|
||||||
|
raise ValidationError("canonical `scenario.version` must be an integer")
|
||||||
|
if not isinstance(initial_step, str) or not initial_step:
|
||||||
|
raise ValidationError("canonical `scenario.initial_step` must be a non-empty string")
|
||||||
|
if not isinstance(steps, list) or not steps:
|
||||||
|
raise ValidationError("canonical `steps_runtime_order` must be a non-empty list")
|
||||||
|
|
||||||
|
step_ids: list[str] = []
|
||||||
|
for idx, step in enumerate(steps, start=1):
|
||||||
|
if not isinstance(step, dict):
|
||||||
|
raise ValidationError(f"canonical step #{idx} must be a mapping")
|
||||||
|
step_id = step.get("step_id")
|
||||||
|
if not isinstance(step_id, str) or not step_id:
|
||||||
|
raise ValidationError(f"canonical step #{idx} missing non-empty `step_id`")
|
||||||
|
step_ids.append(step_id)
|
||||||
|
|
||||||
|
if initial_step not in step_ids:
|
||||||
|
raise ValidationError("canonical initial_step not found in canonical steps_runtime_order")
|
||||||
|
|
||||||
|
return scenario_id, version, step_ids, initial_step
|
||||||
|
|
||||||
|
|
||||||
|
def extract_runtime(runtime: dict) -> tuple[str, int, list[str], str]:
|
||||||
|
scenario = runtime.get("scenario")
|
||||||
|
if not isinstance(scenario, dict):
|
||||||
|
raise ValidationError("runtime missing `scenario` mapping")
|
||||||
|
|
||||||
|
scenario_id = scenario.get("id")
|
||||||
|
version = scenario.get("version")
|
||||||
|
initial_step = scenario.get("initial_step")
|
||||||
|
steps = runtime.get("steps_runtime_order")
|
||||||
|
|
||||||
|
if not isinstance(scenario_id, str) or not scenario_id:
|
||||||
|
raise ValidationError("runtime `scenario.id` must be a non-empty string")
|
||||||
|
if not isinstance(version, int):
|
||||||
|
raise ValidationError("runtime `scenario.version` must be an integer")
|
||||||
|
if not isinstance(initial_step, str) or not initial_step:
|
||||||
|
raise ValidationError("runtime `scenario.initial_step` must be a non-empty string")
|
||||||
|
if not isinstance(steps, list) or not steps:
|
||||||
|
raise ValidationError("runtime `steps_runtime_order` must be a non-empty list")
|
||||||
|
|
||||||
|
step_ids: list[str] = []
|
||||||
|
for idx, step in enumerate(steps, start=1):
|
||||||
|
if not isinstance(step, dict):
|
||||||
|
raise ValidationError(f"runtime step #{idx} must be a mapping")
|
||||||
|
step_id = step.get("step_id")
|
||||||
|
if not isinstance(step_id, str) or not step_id:
|
||||||
|
raise ValidationError(f"runtime step #{idx} missing non-empty `step_id`")
|
||||||
|
step_ids.append(step_id)
|
||||||
|
|
||||||
|
if initial_step not in step_ids:
|
||||||
|
raise ValidationError("runtime initial_step not found in runtime steps_runtime_order")
|
||||||
|
|
||||||
|
return scenario_id, version, step_ids, initial_step
|
||||||
|
|
||||||
|
|
||||||
|
def validate_bundle(runtime_path: Path, canonical_path: Path, template_path: Path) -> None:
|
||||||
|
runtime = read_json(runtime_path)
|
||||||
|
canonical = read_yaml(canonical_path)
|
||||||
|
template = read_yaml(template_path)
|
||||||
|
|
||||||
|
canonical_id, canonical_version, canonical_steps, canonical_initial = extract_canonical(canonical)
|
||||||
|
runtime_id, runtime_version, runtime_steps, runtime_initial = extract_runtime(runtime)
|
||||||
|
template_id, template_version = extract_template_scenario(template)
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
if canonical_id != runtime_id:
|
||||||
|
errors.append(f"scenario id mismatch: canonical={canonical_id} runtime={runtime_id}")
|
||||||
|
if canonical_version != runtime_version:
|
||||||
|
errors.append(f"scenario version mismatch: canonical={canonical_version} runtime={runtime_version}")
|
||||||
|
if canonical_initial != runtime_initial:
|
||||||
|
errors.append(f"initial_step mismatch: canonical={canonical_initial} runtime={runtime_initial}")
|
||||||
|
if canonical_steps != runtime_steps:
|
||||||
|
errors.append("steps_runtime_order mismatch between canonical and runtime")
|
||||||
|
|
||||||
|
if template_id != canonical_id:
|
||||||
|
errors.append(f"template id mismatch: template={template_id} canonical={canonical_id}")
|
||||||
|
if template_version != canonical_version:
|
||||||
|
errors.append(f"template version mismatch: template={template_version} canonical={canonical_version}")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise ValidationError("; ".join(errors))
|
||||||
|
|
||||||
|
print(
|
||||||
|
"[runtime-bundle-validate] ok "
|
||||||
|
f"id={canonical_id} version={canonical_version} steps={len(canonical_steps)} initial={canonical_initial}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Validate conversation bundle runtime/canonical/template coherence.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--runtime",
|
||||||
|
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_runtime.json",
|
||||||
|
help="Path to scenario_runtime.json",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--canonical",
|
||||||
|
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_canonical.yaml",
|
||||||
|
help="Path to scenario_canonical.yaml",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--template",
|
||||||
|
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_promptable_template.yaml",
|
||||||
|
help="Path to scenario_promptable_template.yaml",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_bundle(Path(args.runtime), Path(args.canonical), Path(args.template))
|
||||||
|
except ValidationError as exc:
|
||||||
|
print(f"[runtime-bundle-validate] error -> {exc}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: bash tools/setup/install_validators.sh
|
||||||
|
|
||||||
|
Install Python dependencies required by repository validators.
|
||||||
|
Environment override:
|
||||||
|
PYTHON_BIN Python executable to use (default: python3)
|
||||||
|
USAGE
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||||
|
REQ_FILE="tools/requirements/validators.txt"
|
||||||
|
|
||||||
|
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
||||||
|
echo "[install-validators] error: python interpreter not found: $PYTHON_BIN" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$REQ_FILE" ]]; then
|
||||||
|
echo "[install-validators] error: missing requirements file: $REQ_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$PYTHON_BIN" -m pip install --user -r "$REQ_FILE"
|
||||||
|
echo "[install-validators] ok installed requirements from $REQ_FILE"
|
||||||
Reference in New Issue
Block a user