Files
le-mystere-professeur-zacus/tools/scenario/diff_gameplay_scenarios.py
T
Clément SAILLANT 229c381c3e 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>
2026-02-25 18:17:46 +01:00

114 lines
4.7 KiB
Python
Executable File

#!/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())