{
  "_lisez_moi": "Contrat des rencontres a phases. PHP (tools/game-data-manager/encounter_contract.php) et Godot (scripts/core/encounter_contract.gd) lisent CE fichier: le vocabulaire des phases, des lignes et des surcharges n'existe qu'ici, donc les deux cotes ne peuvent pas diverger. Ce n'est pas une donnee d'auteur editable dans le gestionnaire, c'est le contrat du code. Meme raison que pour audio_contract.json: le projet a deja paye le prix de deux listes jumelles qui divergent.",
  "contract_version": 1,

  "starts": ["first_aggro", "spawn"],
  "engagements": ["until_encounter_end", "perception"],
  "activations": ["latched", "while_true"],
  "clocks": ["sequence", "combat"],
  "preemptions": ["pause", "continue"],
  "after_end": ["hold_last", "release"],
  "transition_modes": ["finish_attack", "interrupt"],
  "kinds": ["boss", "elite"],

  "_conditions": "Meme forme que partout ailleurs dans le jeu: {path, operator, value}, et une liste vaut ET. Pour un OU, deux lignes proposent la meme phase.",
  "operators": ["<=", ">=", "<", ">", "==", "!=", "truthy", "falsy"],

  "_addresses": "Contexte LOCAL et en lecture seule, propre a une instance. Ce ne sont ni des variables partagees ni des adresses que les effets peuvent ecrire. Une cible absente rend le predicat faux, jamais zero. Les adresses segment_only comptent depuis le debut du segment en cours : elles ne valent que dans les conditions de sortie d'un segment (ligne 21), pas pour armer une ligne. Une sortie se juge OU : la premiere condition vraie fait passer au segment suivant, et la fin dessinee reste un plafond.",
  "addresses": {
    "encounter.health_ratio": {"label": "PV restants (ratio)", "min": 0.0, "max": 1.0},
    "encounter.combat_elapsed": {"label": "Temps de combat (s)", "min": 0.0, "max": 100000.0},
    "encounter.target_distance": {"label": "Distance a la cible (px)", "min": 0.0, "max": 100000.0},
    "encounter.segment_elapsed": {"label": "Temps dans le segment (s)", "min": 0.0, "max": 100000.0, "segment_only": true},
    "encounter.movement_arrived": {"label": "Mouvement sur rail arrive (1 = oui)", "min": 0.0, "max": 1.0, "segment_only": true},
    "encounter.segment_hero_hits": {"label": "Coups portes au heros (segment)", "min": 0.0, "max": 1000.0, "segment_only": true},
    "encounter.segment_hits_taken": {"label": "Coups recus (segment)", "min": 0.0, "max": 1000.0, "segment_only": true},
    "encounter.segment_damage_ratio": {"label": "Degats recus (segment, part des PV)", "min": 0.0, "max": 1.0, "segment_only": true},
    "encounter.segment_attacks_done": {"label": "Attaques finies (segment)", "min": 0.0, "max": 1000.0, "segment_only": true}
  },

  "_overridable": "Ce qu'une phase a le droit de surcharger. Absence d'un champ = heriter. Un champ present a false, 0 ou [] est un CHOIX, pas une absence. Les listes d'attaques se remplacent entierement, jamais par position. Resistances, immunites et types du contact aussi: une phase les remplace en bloc.",
  "overridable": [
    "speed",
    "hero_response",
    "aggro_response",
    "aggro_movement",
    "detected_movement",
    "undetected_movement",
    "attack_needs_sight",
    "attacks",
    "direct_hit",
    "direct_damage_min",
    "direct_damage_max",
    "direct_hit_cooldown",
    "direct_hit_knockback",
    "direct_hit_angle",
    "direct_hit_reach",
    "knockback_resistance",
    "color",
    "defense",
    "damage_resistances",
    "effect_immunities",
    "direct_hit_damage_types"
  ],

  "_forbidden": "Interdit dans une surcharge: une phase ne change ni la famille, ni l'eligibilite d'apparition, ni le corps physique, ni la reserve de PV. Changer ces champs en plein combat ferait grossir un corps dans un mur, doublerait une recompense ou casserait la barre de vie.",
  "forbidden": [
    "display_name",
    "health",
    "spawn_weight",
    "spawn_min",
    "spawn_max",
    "min_size",
    "max_size",
    "shape",
    "hurtbox_length",
    "hurtbox_radius",
    "drops",
    "model_3d",
    "levels",
    "encounter",
    "detection_range",
    "detection_angle",
    "aggro_on_detection",
    "aggro_on_damage"
  ],

  "_defense": "La protection d'une phase, resolue a part de celle que la frise d'attaque pose deja. Bouclier, armure, faiblesse et resistance ne sont pas des synonymes: les garder distincts est ce qui permet d'ecrire une carapace qu'on ne peut ni blesser ni interrompre, puis une recharge vulnerable.",
  "defense": {
    "invulnerable": {"kind": "bool", "default": false},
    "super_armor": {"kind": "bool", "default": false},
    "damage_taken_multiplier": {"kind": "number", "default": 1.0, "min": 0.0, "max": 100.0},
    "knockback_resistance": {"kind": "number", "default": 0.0, "min": 0.0, "max": 1.0}
  },

  "_bounds": "Bornes des nombres du contrat. Un segment nul est refuse: une phase doit durer au moins un tick de simulation.",
  "bounds": {
    "segment_start": {"min": 0.0, "max": 100000.0},
    "segment_end": {"min": 0.0, "max": 100000.0},
    "segment_min_seconds": 0.05,
    "min_active_seconds": {"min": 0.0, "max": 600.0},
    "activation_delay_seconds": {"min": 0.0, "max": 60.0},
    "rearm_delay_seconds": {"min": 0.0, "max": 60.0},
    "max_wait_seconds": {"min": 0.0, "max": 60.0},
    "loop_iterations": {"min": 1, "max": 9999},
    "phases_per_level": {"min": 1, "max": 32},
    "tracks_per_level": {"min": 0, "max": 16},
    "segments_per_track": {"min": 1, "max": 64}
  },

  "_defaults": "Valeurs posees quand l'auteur n'ecrit rien. Une rencontre absente ou desactivee doit laisser la creature se comporter exactement comme avant ce chantier.",
  "defaults": {
    "enabled": false,
    "kind": "boss",
    "start": "first_aggro",
    "engagement": "until_encounter_end",
    "activation": "latched",
    "clock": "sequence",
    "on_preempted": "pause",
    "after_end": "hold_last",
    "transition_mode": "finish_attack",
    "max_wait_seconds": 0.75,
    "min_active_seconds": 0.0,
    "activation_delay_seconds": 0.0,
    "rearm_delay_seconds": 0.25,
    "urgent": false
  }
}
