# GAB-303 · DocumentLearningObserveDescribe — « Observer ≠ interpréter »

**Format :** GAB data-driven (moule GAB-VALIDE/GAM) · **renderer_key :** `observe_zones` · **family_code :** `document_analysis`
**Critère validé :** changer le JSON change l'observation guidée sans modifier le HTML. ✅ (check.py ; Playwright à faire)

## Pack (format GAB-VALIDE attendu)
```
GAB-303/
  renderer.html                     ← moteur observe_zones générique (ne pas modifier par instance)
  instance-observe-describe.json    ← SOURCE DE VÉRITÉ (le dev remplit ça)
  schema.json                       ← contrat de validation
  README-contract.md                ← ce fichier
```

## Le JSON déclare
- `gab_id: GAB-303` · `renderer_key: observe_zones` · `family_code: document_analysis` · `site`
- `instance.title` — titre d'écran
- `instance.document_ref { src, kind, alt }` — réf média du document observé
- `instance.observation_prompt` — consigne (« décris ce que tu VOIS »)
- `instance.modes[]` — `{ id, label, active }` : toujours `obs` (observer) + `int` (interpréter)
- `instance.observable_zones[]` — chaque zone : `{ id, emoji, fact, pos }`. **`fact`** = UNIQUEMENT ce qu'on VOIT, jamais une interprétation
- `instance.observe_order[]` — ids des zones dans l'ordre d'observation suggéré
- `instance.description_rules { observe_feedback, interpret_block, all_observed }` — libellés de feedback
- `accessibility[]` / `child_safety[]` — `facts_only_no_interpretation`, `no_invented_observation`, `no_hard_failure`

## Comment le dev ajoute une zone
```json
{ "id":"zone-4", "emoji":"⚖️", "fact":"On VOIT une balance posée au sol.", "pos":{ "top":"120px","left":"40px","width":"70px","height":"60px" } }
```
Il l'ajoute dans `instance.observable_zones` (et son id dans `observe_order`). Le moteur peint la zone, gère le clic et le décompte tout seul. **Il ne touche jamais renderer.html.**

## Le moteur (observe_zones) fait
- peint chaque zone cliquable depuis `instance.observable_zones` (position via `pos`)
- mode **Observer** : clic = affiche le `fact` (ce qu'on VOIT) + marque la zone observée
- mode **Interpréter** : clic = bloque et rappelle `interpret_block` (l'interprétation = étape 305)
- quand toutes les zones sont observées : affiche `all_observed` + passe l'état à `ready` (prêt pour 304)
- BLOCKED si `observable_zones` vide / une `fact` manquante / `modes` sans `obs` / `document_ref` absent

## Garde-fous (child_safety)
- `facts_only_no_interpretation` : chaque zone porte un fait visible, jamais une interprétation.
- `no_invented_observation` : on ne décrit que ce qui est dans le document, rien d'halluciné.
- `no_hard_failure` : le mode « interpréter » ne casse pas, il recadre pédagogiquement.

## Inline vs externe
Inline (`window.GAB_INSTANCE`) = copie de démo pour ouvrir sans serveur. **Source de vérité = le .json.**
Moteur prêt pour l'externe : `DATA = (arguments.length && ext) ? ext : (window.GAB_INSTANCE || null)`.

## Source d'origine
`GAB-001-390-DATA-SOURCES/INDEX-300-documentlearning-GAB-301-305-PLAYABLE.html` (stage `data-tpl="303"`). Contenu extrait des 3 zones (👤 personnage corpulent assis / 🧍🧍 deux personnages qui portent le premier / 📜 parchemin avec sceau), des 2 modes (Observer/Interpréter) et des feedbacks des handlers `d303Mode(...)` / `d303Zone(...)`.
