{
  "contract_version": "gab_071_contract_v1",
  "gab_id": "GAB-071",
  "canonical_name": "StoryAudioNarratorScene",
  "module_owner": "EdTechStoryLearning",
  "renderer_key": "media_viewer",
  "required_fields": [
    "gab_id",
    "scene_id",
    "transcript"
  ],
  "optional_fields": [
    "title",
    "body",
    "persona",
    "audio_src",
    "accessibility"
  ],
  "field_types": {
    "scene_id": "string — identifiant unique de la scène",
    "title": "string — titre affiché en en-tête de carte",
    "body": "string — texte d'introduction ou de contexte de la scène",
    "transcript": "string — texte narré OBLIGATOIRE, affiché en permanence (accessibilité + fallback TTS)",
    "persona": "object{name:string, avatar:string, role:string} — identité du narrateur",
    "audio_src": "string|null — URL du fichier audio ; null ou absent = fallback Web Speech API sur transcript",
    "accessibility": "object{transcript_always_visible:boolean, fallback:string}"
  },
  "constraints": [
    "transcript est obligatoire et toujours affiché — audio sans transcript est INTERDIT (spec source).",
    "audio_src peut être null ou absent : le moteur active alors la synthèse vocale (Web Speech API) sur le transcript.",
    "persona.avatar est un emoji ou une URL image — ne pas hardcoder dans le HTML.",
    "transcript BLOCKED si chaîne vide ou absente."
  ],
  "blocked_conditions": [
    "transcript absent",
    "transcript vide",
    "scene_id absent"
  ],
  "accessibility": [
    "transcript_always_visible",
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "fallback_tts_or_visual"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, player actif, transcript visible, 0 erreur" },
    { "case": "transcript absent", "expected": "BLOCKED listant le champ manquant" },
    { "case": "audio_src null", "expected": "fallback Web Speech API sur transcript, player reste actif" },
    { "case": "persona absent", "expected": "rendu dégradé sans info narrateur, pas de crash" },
    { "case": "instance externe injectée via init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" },
    { "case": "prefers-reduced-motion", "expected": "animation waveform réduite ou désactivée" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-071",
    "note": "Ce schema VALIDE l'instance. Le contrat pédagogique complet (input_contract/validation_logic/feedback_scoring_logic) vit dans le CORE-GAB officiel, pas ici (évite la duplication). renderer_key 'media_viewer' : pas de moteur kit de référence — renderer dérivé de l'écran source (voir README)."
  }
}
