# Contrat de déblocage des meubles

Ce document est la recette obligatoire lorsqu’un meuble ouvre une nouvelle
fonction du restaurant. Le casier d’employés, le comptoir takeout et l’ardoise
emploient cette même chaîne de données.

## Source de vérité

La disponibilité d’un meuble vient de sa **quantité ouverte**, jamais d’un effet
écrit dans `restaurant_elements` et jamais d’une condition ajoutée au meuble.

```text
upgrade achetée
  -> modificateur flat/add sur <fonction>_count
  -> restaurant_elements.<meuble>.max_count_variable
  -> GameState.restaurant_element_open_count(<meuble>)
  -> 0 : meuble absent de la palette / fonction indisponible
  -> 1+ : autant d’exemplaires ouverts, bornés par max_count
```

`max_count` est toujours le plafond absolu. La statistique peut dépasser ce
plafond sans ouvrir davantage d’exemplaires.

## Recette pour une nouvelle fonction portée par un meuble

1. Créer le `kind` du comportement dans les vocabulaires Godot, PHP et JS.
2. Créer une statistique entière `<fonction>_count`, minimum `0`.
3. Mettre sa `base_value` à `0` si le meuble commence verrouillé.
4. Mettre `max_count_variable: "<fonction>_count"` sur le meuble et régler son
   `max_count` au plafond absolu voulu.
5. Créer un nœud d’upgrade dont le modificateur vise **Statistique** : variable
   `<fonction>_count`, couche `flat`, opération `add`, valeur positive.
6. Dans le gameplay, tester la disponibilité avec
   `restaurant_element_kind_is_unlocked(kind)` et la présence physique avec
   `restaurant_element_kind_is_placed(kind)`. Ce sont deux questions distinctes.
7. Si un onglet doit rester connu après un reset de dimension, appeler
   `restaurant_feature_is_unlocked(meta_key, kind)`. Le compteur reste de portée
   dimension; seul le souvenir d’avoir découvert l’interface est méta.
8. Ajouter les éventuels prérequis physiques dans `placement_requires`. Ils
   contrôlent la pose et l’ordre de retrait, pas le déverrouillage.
9. Ajouter un cas générique ou spécifique au smoke test et au test
   `restaurant_element_test.js`.

## Exemple du casier

```text
restaurant_elements.employee_locker
  kind               = employee_locker
  max_count           = 1
  max_count_variable  = employee_locker_count
  placement_requires  = [basic_shelf, basic_order_board]

game_variables.employee_locker_count
  type        = int
  base_value  = 0
  minimum     = 0
  maximum     = 1

upgrade_nodes.casier.modifiers[0]
  variable   = employee_locker_count
  layer      = flat
  operation  = add
  value      = 1
```

## Ce qui est volontairement séparé

- **Débloqué** : le compteur est supérieur à zéro.
- **Posé** : un exemplaire ouvert est actuellement sur le plancher et ses
  `placement_requires` sont satisfaits.
- **Interface déjà découverte** : un drapeau méta permet de garder l’onglet.
- **Contenu visible dans l’interface** : les `conditions` de ce contenu, par
  exemple celles d’un employé, décident s’il apparaît dans sa liste.

Ne pas utiliser l’espace d’effet **Meuble** : il est en lecture seule. Ne pas
réintroduire `station_functions` et ne pas coder le nom d’un nœud d’upgrade dans
le gameplay.

## Garde-fous

La publication PHP et le chargement Godot refusent maintenant :

- une statistique de limite qui n’est pas un entier non négatif;
- un meuble dont la quantité commence à zéro sans aucune upgrade qui ajoute un
  bonus plat positif à cette statistique.

Les mêmes invariants sont vérifiés dans les données maître et brouillon par
`restaurant_element_test.js`, puis le smoke test vérifie le comportement réel.
