# 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
```

## Primitives de repli et vrais modèles

`data/shapes_3d.json` garde **la primitive de repli** et **l'élévation**. Un
acteur peut maintenant ajouter un bloc optionnel `model_3d` dans
`game_data_master.json` : chemin GLB, calage visuel et correspondance entre les
états du jeu et les clips du fichier. Tout ce qui touche au jeu — emprise,
collision, portée, vitesse — reste inchangé et piloté par la 2D.

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.

## Les outils PHP

Le gestionnaire principal `tools/game-data-manager/` porte la section **Modèle
et animations 3D** directement dans les fiches Joueur, Monstres, Clients et
Employés. Il balaie `assets/`, propose les `.glb` disponibles et lit leurs noms
de clips automatiquement.

`tools/shapes-3d-manager/index.php` reste volontairement petit : il ne règle que
la primitive et la hauteur de repli dans `shapes_3d.json`. Il est utile quand un
acteur n'a pas encore de modèle ou si un GLB ne peut pas être chargé.

## 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/model_3d_factory.gd` | chargement, calage automatique et animation des GLB |
| `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 des primitives de repli |
| `tools/game-data-manager/` | le gestionnaire principal, y compris modèles et clips 3D |

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

Le héros livre maintenant un GLB riggé et animé. Les autres acteurs restent sur
leurs primitives ou leurs visuels 2D tant qu'aucun modèle ne leur est choisi ;
le même formulaire est déjà disponible sur leur fiche. Un export sans clip Idle
emploie la première pose de marche comme posture neutre.

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.
