{
  "contract_version": "gab_159_contract_v1",
  "gab_id": "GAB-159",
  "canonical_name": "GameComboStreakLocal",
  "module_owner": "EdTechGameLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "combo_id",
    "title",
    "streak_label",
    "multiplier_levels",
    "msg_initial"
  ],
  "optional_fields": [
    "progress_labels",
    "cta_ok",
    "cta_ko",
    "msg_broken",
    "msg_bonus_x3",
    "msg_bonus_x5",
    "msg_sustain_x5",
    "msg_progress_template",
    "accessibility"
  ],
  "field_types": {
    "combo_id": "string — identifiant unique de l'instance combo",
    "title": "string — titre affiché dans le hero",
    "streak_label": "string — label du compteur affiché dans la card",
    "multiplier_levels": "array<{threshold:number, multiplier:number, label:string}> — seuils de passage (trié par threshold croissant)",
    "progress_labels": "object{level_start:string, level_bonus:string, level_max:string} — libellés des jalons de la barre",
    "cta_ok": "object{label:string, action:string} — bouton bonne action",
    "cta_ko": "object{label:string, action:string} — bouton erreur / cassure combo",
    "msg_initial": "string — message affiché avant toute action",
    "msg_broken": "string — message quand le combo est cassé (ko)",
    "msg_bonus_x3": "string HTML — message quand le seuil x3 est atteint",
    "msg_bonus_x5": "string HTML — message quand le seuil x5 est atteint",
    "msg_sustain_x5": "string HTML — message quand on maintient le combo max",
    "msg_progress_template": "string — template avec {count} pour les étapes intermédiaires",
    "accessibility": "object{aria_label_ok:string, aria_label_ko:string}"
  },
  "constraints": [
    "multiplier_levels doit contenir au moins 1 entrée avec threshold:0 (état initial).",
    "msg_initial requis — affiché avant toute interaction.",
    "Le multiplicateur LOCAL est perdable (une erreur remet à threshold 0) — usage intra-jeu uniquement.",
    "Ne pas utiliser pour un streak global gamification (→ PlayKit) ni pour le scoring d'exercice (→ GAB-129)."
  ],
  "blocked_conditions": [
    "gab_id absent",
    "title absent ET streak_label absent",
    "multiplier_levels vides ou non-array",
    "msg_initial absent"
  ],
  "accessibility": [
    "aria_label sur les boutons ok/ko (via instance)",
    "focus_visible",
    "prefers_reduced_motion",
    "messages d'état lisibles par lecteur d'écran"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, multiplicateur x1 initial, 0 erreur" },
    { "case": "champ requis manquant (ex: multiplier_levels vide)", "expected": "BLOCKED listé dans panel-err" },
    { "case": "clic Bonne action × 3", "expected": "multiplicateur passe à x3, message bonus x3, flamme agrandie" },
    { "case": "clic Bonne action × 5", "expected": "multiplicateur passe à x5, message max, flamme max" },
    { "case": "clic Erreur après combo", "expected": "combo remis à 0, message broken, barre vidée" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, boutons empilés en mobile" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-159",
    "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)."
  }
}
