# BIG EGG 3D — la branche de travail

**Depuis le 27/08/2026, c'est ici qu'on travaille.** Ce dossier contient le jeu
entier — mêmes scripts, mêmes données, mêmes règles — rendu en primitives 3D par
un pont. Toute correction et tout nouveau contenu y vont.

`../big-egg/` est l'ancienne branche 2D, conservée **en lecture seule** comme
référence : on l'ouvre pour comparer ou retrouver une version antérieure, jamais
pour y écrire. Les directives complètes du projet sont dans `../CLAUDE.md`.

## Le principe, en une phrase

**Le jeu 2D tourne tel quel et pilote des avatars 3D.** Ce n'est pas un portage :
c'est le jeu, inchangé, avec un pont qui lui donne un corps.

Concrètement, `autoload/bridge_3d.gd` fait deux choses et rien d'autre :

1. il **éteint le dessin 2D** de ce qui a un corps dans le monde ;
2. il **donne à chaque entité un avatar 3D** qu'il garde collé à elle.

La 2D reste donc la **seule autorité** : positions, collisions, portées, plan de
la salle, cuisine, clients, brigade, sauvegardes. La 3D ne décide de rien, elle
montre. C'est ce qui permet à cette branche de suivre le jeu sans se recoder à
chaque changement.

**Preuve que les fonctions sont intactes :** le smoke test du jeu tourne ici
**sans une modification** et passe.

```bash
GODOT=C:/Users/ELR17/Downloads/Godot_v4.7.1-stable_win64.exe/Godot_v4.7.1-stable_win64_console.exe

"$GODOT" --path big-egg-3d                                                    # jouer
"$GODOT" --headless --path big-egg-3d res://scenes/main/smoke_test.tscn        # les regles du jeu
"$GODOT" --headless --path big-egg-3d res://scenes/main/bridge_3d_check.tscn   # le pont
php big-egg-3d/tools/shapes-3d-manager/tests/mirror_test.php                   # le miroir de l outil
```

## Ce que la 3D ajoute à la donnée : deux champs

**La primitive** et **l'élévation**, dans `data/shapes_3d.json`. Rien d'autre.
Tout le reste — nom, emprise au sol, couleur, biome, comportement — continue de
vivre dans `game_data_master.json`, partagé avec le jeu.

Le champ vertical s'appelle **`tall`**, jamais `height` : côté auteur, `height`
désigne déjà la profondeur de l'emprise au sol d'un meuble.

**Une seule règle d'unités : 1 pixel du jeu = 1 unité 3D.** Le pont de
coordonnées est `Vector2(x, y) → Vector3(x, 0, y)`, et c'est tout.

## L'outil PHP

`tools/shapes-3d-manager/index.php`, à ouvrir sous WAMP. **Ce n'est pas un fork**
du gestionnaire du jeu : il lit le fichier maître en lecture seule pour lister ce
qui existe, et n'écrit que dans `shapes_3d.json`. Dupliquer 450 Ko de PHP et de
JS pour deux champs aurait créé deux vérités qui divergent à la première
modification. Une entité restée sur le défaut de sa collection n'est pas stockée.

## Fichiers propres à la branche

| | |
|---|---|
| `autoload/bridge_3d.gd` | le pont : avatars, sol, caméra, extinction du dessin 2D |
| `scripts/core/shape_3d_factory.gd` | une primitive par entité, **et le pont de coordonnées** |
| `scripts/core/camera_3d_rig.gd` | caméra 3/4 **orthographique**, cadrage et suivi |
| `scripts/core/bridge_3d_check.gd` | le filet du pont |
| `data/shapes_3d.json` | primitive et élévation par entité |
| `tools/shapes-3d-manager/` | l'outil qui écrit ce fichier |

Tout le reste est le jeu, à l'identique, à **neuf lignes près** : une garde
`if Bridge3D.active: return` en tête du `_draw()` de chaque corps du monde
(restaurant, donjon, joueur, monstre, client, employé, assiette, ramassable,
meuble). L'interface, les bulles, les nombres de dégâts et l'arbre d'upgrades
restent en 2D par-dessus la scène.

## Cinq pièges déjà payés

**Le recul d'une caméra inclinée se DIVISE.** Pour descendre de `HEIGHT`, le
rayon parcourt `HEIGHT / tan(inclinaison)` en profondeur. Avec une
multiplication, la caméra visait 700 unités au-delà du point voulu et on ne
voyait que le quart bas de la salle.

**`size` d'une caméra orthographique est l'étendue de l'IMAGE, pas du sol.** Un
sol vu en biais est comprimé de `sin(inclinaison)` : une profondeur de 870 unités
n'occupe que 686 de hauteur d'image.

**Un `await` dans une fonction de contrôle en fait une coroutine.** L'appelant
reprend la main au premier `await`, annonce « OK » et quitte avant la moitié des
contrôles. Un test qui ne s'exécute pas est pire qu'un test faux. Corollaire :
un repli qui rend la valeur attendue (`return focus` quand le calcul est
impossible) fait comparer une valeur à elle-même — `ground_aim()` rend donc
`Vector2.INF`, jamais une valeur complaisante.

**Une garde `_draw()` éteint l'information en même temps que le corps.** Ce que
la 2D dessinait ne se limitait pas à l'apparence: le secteur d'une attaque, la
vie d'une créature, l'avancement d'une cuisson **disent l'état du jeu**. Éteint
d'un bloc, le combat devient illisible — un monstre chargeait son coup sans que
rien ne l'annonce. Tout ce qui informe doit **remonter dans la scène 3D**:
`InfoBadge3D` au-dessus de la tête, `AttackField3D` au sol. Devant chaque garde
ajoutée, se demander ce que ce `_draw()` disait en plus de sa forme.

**Une salle change de forme sans changer de scène.** `advance_to_next_room()`
vide la salle et recalcule `arena` **sur place**: le chemin du fichier ne bouge
pas. Le pont, qui ne surveillait que lui, gardait les murs et le cadrage de la
salle précédente — dans une salle qui défile, le joueur passait au travers d'un
mur resté petit et la caméra ne le suivait pas, son axe ayant été déduit d'une
arène disparue. La clé de reconstruction porte donc la scène **et**
`world_bounds()`.

## La rue

Son côté est **déclaré**, pas supposé: `Restaurant.STREET_SIDE` vaut `north`,
`west` ou `east`, et tout en découle — trottoir, porte percée dans la bonne
façade, files, sens de marche des passants. La branche est à `east`.

Le **comptoir takeout garde sa façade nord** quelle que soit la rue. Le faire
suivre entraînerait son ancrage déclaré dans la donnée (`anchor: north_wall`),
ses deux zones de service — les deux moitiés de sa face accessible, qui pivote
avec lui — et la règle de pose. C'est un chantier à part. En attendant, le mur
nord porte une **vitrine**: son tronçon devant le comptoir est abaissé, on voit
la file par-dessus.

Côté passants, `Customer._crossed_x()` projette la position sur l'axe déclaré au
lieu de ne lire que l'abscisse: sans ça, un passant ne franchissait jamais sa
porte dès que la rue cessait d'être horizontale.

## Trois interrupteurs

| touche | effet |
|---|---|
| **F2** | bascule l'affichage: **primitives** ↔ **visuels 2D** (les 313 feuilles déjà produites, dressées face à la caméra) |
| **F3** | l'afficheur de diagnostic (cadence, focus fenêtre, touche physique, vecteur de déplacement) |

Et deux drapeaux dans `bridge_3d.gd`, prévus pour être coupés sans rien casser :

- `varied` — la variété de forme, d'élévation et de teinte des créatures et des
  clients. Elle est **dérivée de leur identifiant**, pas tirée au sort: le même
  monstre a toujours la même allure d'une partie à l'autre, et sa teinte part de
  la palette de son biome — une région forme une famille. Une entrée explicite
  dans `shapes_3d.json` l'emporte toujours.
- `show_names` — le nom au-dessus des têtes, en retrait et plus terne que
  l'information de jeu. Un aide-mémoire de développement, à couper le jour où la
  salle se lit sans lui.

Le mode **visuels 2D** ne remplace pas les primitives: il permet de voir la
salle habillée de l'art existant pendant que la 3D se construit. Il ne prend que
la **première case** de chaque feuille — la vue sud au repos: rejouer l'animation
directionnelle en 3D demanderait de refaire la logique de `VisualHolder`. Une
entité sans art retombe sur son volume plutôt que de disparaître.

## Limites assumées

Les avatars sont des **primitives** : boîtes, cylindres, sphères, capsules. Le
but n'est pas la ressemblance mais l'architecture — si le montage tient avec des
cubes, y glisser des modèles générés ne touchera que `Shape3DFactory.build()`.

Les entités n'ont pas d'animation : elles se déplacent et s'orientent, elles ne
marchent pas. C'est ce qu'un rig apporterait.

Le tri en profondeur est natif : le paramètre `front_depth` envisagé côté 2D (le
devant solide contre l'arrière franchissable) est **sans objet** ici.
