Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
src/commit.h
mcp/
Game.exe
Editor.exe
lib/*
Expand Down
100 changes: 100 additions & 0 deletions docs/OPEN_QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Questions ouvertes — à valider avant d'écrire du code moteur (Phase 3)

Statut : **Phases 0 à 4 terminées et compilées (Debug + Release, 0/0).** Cf. `docs/PHASE3-4-IMPLEMENTATION.md`.
Questions #1–#3 tranchées par l'utilisateur le 2026-09-06, #1 implémentée :
- **#1 → Option 1**, implémentée. Câblage réel = **3 fichiers de build existants** (un de plus que prévu) + `Engine.cpp`, tout sous `#if VG_ENABLE_MCPBRIDGE`, gate runtime par variable d'env `VG_MCP_BRIDGE` :
- `sharpmake/vg.solution.sharpmake.cs` : `conf.AddProject<MCPBridge>(target);` (1 ligne)
- `sharpmake/vg.engine.sharpmake.cs` : `conf.Defines.Add("VG_ENABLE_MCPBRIDGE");` (1 ligne)
- `sharpmake/vg.data.sharpmake.cs` : `SourceFilesExcludeRegex.Add(@".*\\mcpbridge(\.*)?");` dans le projet `Version` (1 ligne — sinon le projet utilitaire `version` ramasse les `.cpp` du nouveau module, comme pour tous les autres modules déjà listés là)
- `src/engine/Engine.cpp` : include gardé + pointeur statique + create/Init + Tick + Deinit (~25 lignes, 5 emplacements, tous `#if VG_ENABLE_MCPBRIDGE`, aucune ligne existante modifiée)
- **#2 → contrat quaternion** confirmé.
- **#3 → `spawn_object` inclus en V1** — fait.

Historique des questions ci-dessous.

---

## #1 — Point d'insertion du bridge dans la boucle moteur (BLOQUANT)

### Constat (cf. `engine-analysis.md` §4, §5, §7, §9)

Il n'existe **aucun point d'extension propre** pour brancher du code externe sans toucher au cœur :
- pas de scripting runtime ;
- pas de système de commandes console enregistrables ;
- pas de scan d'un dossier de plugins — chaque DLL est chargée par un appel explicite `Plugin::create<T>("nom")` codé en dur (`src/engine/Engine.cpp`) ;
- l'auto-registration de classes (`AutoRegisterClassInfo`) ne se déclenche que si la DLL qui contient ces classes est chargée.

Un module bridge additif (`src/mcpbridge/…` + `sharpmake/vg.mcpbridge.sharpmake.cs`) est **entièrement du code neuf**, mais pour qu'il tourne il faut au minimum :

| # | Modif | Fichier | Ampleur | Statut règle d'or |
|---|---|---|---|---|
| a | `conf.AddProject<MCPBridge>(target);` | `sharpmake/vg.solution.sharpmake.cs` (existant) | 1 ligne | ⚠️ ajout dans un fichier existant |
| b | Chargement de la DLL + tick chaque frame | 1 point dans `src/engine/Engine.cpp` **ou** `src/application/…` (existant) | ~3–5 lignes | ⚠️ ajout dans un fichier existant |

> Le fichier `vg.mcpbridge.sharpmake.cs` lui-même est auto-inclus (`sharpmake/main.sharpmake.cs:5`) → aucune modif de build pour être *compilé*, seulement pour être *lié à la solution et chargé*.

### Options proposées (à trancher par l'utilisateur)

- **Option 1 — Assumer les 2 ajouts (a) + (b).**
Diff minimal, purement additif (aucune ligne existante supprimée/modifiée). C'est la voie recommandée.
Diff exact proposé pour (b), à valider :
```cpp
// src/engine/Engine.cpp, dans Engine::init() après la création des autres plugins
#if VG_ENABLE_MCPBRIDGE
m_mcpBridge = Plugin::create<mcpbridge::IMCPBridge>("mcpbridge"); // no-op si DLL absente
#endif
// ... dans RunOneFrame(), dans le bloc ToolUpdate déjà existant (Engine.cpp:979-993) :
#if VG_ENABLE_MCPBRIDGE
if (m_mcpBridge) m_mcpBridge->Tick();
#endif
```
Gardé derrière `#if VG_ENABLE_MCPBRIDGE` (défini seulement par le projet bridge) → **zéro impact** sur le build normal quand le bridge est désactivé (checklist Phase 6).

- **Option 2 — Héberger le bridge dans le projet `game` existant.**
`projects/game/` est déjà chargé par l'éditeur (`Engine.cpp:424`) et a un `ToolUpdate`. Mais cela modifie `projects/game/src/Game.cpp` (fichier existant) → **plus** invasif que l'option 1, et couple le bridge au jeu. Non recommandé.

- **Option 3 — Process externe uniquement, pas de code moteur.**
Le serveur MCP lit/écrit directement les fichiers `.scene` XML sur disque. **Rejeté** : ne fonctionne pas sur une scène chargée en mémoire (pas de rechargement à chaud), donc pas de feedback visuel live — contredit le cas d'usage.

### Décision attendue

➡️ **Quelle option ?** (défaut recommandé : Option 1)
➡️ Si Option 1 : le point de chargement va dans `engine` ou dans `application` ?

---

## #2 — Contrat de rotation (résolu, à confirmer)

Le moteur stocke le transform en `float4x4` (pas de quaternion). Le plan (Phase 2) recommande de garder le **quaternion** dans le contrat MCP et de convertir côté bridge.

➡️ **Confirmation** : contrat MCP en quaternion `{x,y,z,w}`, conversion dans le module C++ via hlslpp + `TRSToFloat4x4` (`src/core/Math/Math.h:129`). OK ?

*(Aucune action requise si d'accord — c'est l'hypothèse retenue dans `docs/data-contract.md`.)*

---

## #3 — Point bloquant §2 du plan : pool vs création dynamique (résolu)

`engine-analysis.md` §8 : `IObject::Instanciate()` existe et est déjà utilisé par le copier-coller éditeur.
→ La **création dynamique (a)** est possible. V1 peut inclure `spawn_object`.

➡️ **Confirmation de périmètre V1** : inclut-on `spawn_object` (clone du prefab `Box1X1M`) dès V1, ou on s'en tient strictement à `list/get/set_transform` sur objets existants pour la première itération ?

*(Le plan Phase 5 penche pour inclure le spawn si (a) est vrai. Hypothèse retenue : `spawn_object` inclus en V1, mais implémenté après validation des 3 fonctions de base.)*

---

## #4 — Cube de référence + convention de rotation — **RÉSOLU (2026-09-06)**

✅ **Primitive de référence = `data/Prefabs/Box_Base.prefab`** (et non `Box1X1M`), tranché par l'utilisateur.
- **Pivot au centre de la base** du mesh → `position.z` = niveau du sol, `scale.z` = hauteur totale, aucun offset de demi-hauteur.
- **Monde Z-up** : X/Y = plan du sol, Z = hauteur (confirmé par la gravité `(0,0,-9.81)` et l'étendue des 1435 transforms d'`Aiguelongue.scene` : X 170 / Y 237 / **Z 38**). Le `target_axes.up = POSITIVE_Y` de l'importeur FBX ne concerne que la conversion à l'import — ne pas s'y fier.
- Dimensions : **mesurées à l'exécution** plutôt que devinées — le bridge renvoie `bounds_local` et `size_world` via `IGameObject::TryGetAABB()`.

✅ **Convention de rotation validée.** Le module décompose/recompose le transform lui-même (`SceneBridge.cpp`, base I/J/K **en lignes** = axes locaux ; quaternion via Shepperd) sans passer par `TRSToFloat4x4(T, quaternion, S)` (`Math.h:129`), dont la surcharge quaternion s'est révélée incohérente avec le reste du moteur (translation placée en 4ᵉ **colonne** au lieu de 4ᵉ ligne).

Deux vérifications faites en Phase 5, dans cet ordre — la première ne suffisait pas :

1. **Round-trip** (set puis get). A d'abord échoué : envoyé `z=+0.7071`, relu `z=−0.7071`. Cause : `decompose` lisait la matrice **transposée** de ce que `recompose` écrivait, ce qui négate l'axe. Corrigé en prenant les termes antisymétriques comme `(r_ij − r_ji)` au lieu de `(r_ji − r_ij)` ; les termes symétriques sont inchangés.
⚠️ Un test à **90° ne peut pas révéler une erreur de signe** (une boîte est symétrique) — le test doit se faire à 45°.
2. **Signe vs le moteur.** Le round-trip seul ne prouve rien : il est cohérent avec lui-même. Source de vérité indépendante utilisée : l'Inspector de l'éditeur, qui affiche les angles d'Euler via `Float4x4ToTRS` — **du code moteur, pas celui du bridge**. Yaw envoyé `+45°` → Inspector affiche **`+45`**. Conventions alignées.
129 changes: 129 additions & 0 deletions docs/PHASE3-4-IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Phases 3-4 — Implémentation (module moteur + serveur MCP)

État : **compilé Debug + Release, 0 erreur / 0 warning** (solution complète) et **validé contre le moteur réel**
via le protocole MCP complet sur la scène `Aiguelongue` (7832 objets) — 2026-09-06.
Reste à jouer : le scénario « plan de ville » depuis Claude Code (§ Phase 5).

---

## Fichiers ajoutés (100 % additifs)

### Module moteur — `src/mcpbridge/`
| Fichier | Rôle |
|---|---|
| `IMCPBridge.h` | Interface plugin (`core::IPlugin`) : `Init(IEngine*, Singletons&)`, `Deinit()`, `Tick()`, `IsEnabled()` |
| `MCPBridge.{h,cpp}` | Plugin + `core::Singleton`. Transport fichier : poll `mcp/commands.jsonl` (mtime), écrit `mcp/state.json` (atomique via `MoveFileEx`). Gate runtime : variable d'env `VG_MCP_BRIDGE` |
| `SceneBridge.{h,cpp}` | Accès scène : `list_objects`, `get_transform`, `set_transform`, `spawn_object`, `save_world`. Résolution d'id via `core::IFactory::FindByUID`. Décomposition/recomposition matricielle maison (position + scale exacts) |
| `Json.{h,cpp}` | Mini-JSON autonome (parse + serialize), sous-ensemble du contrat |
| `Precomp.{h,cpp}`, `mcpbridge.def` | Boilerplate plugin (mirroir de `src/physics`, `src/audio`) |

### Build
| Fichier | Rôle |
|---|---|
| `sharpmake/vg.mcpbridge.sharpmake.cs` | **Nouveau** projet `MCPBridge` (`Type.DynamicLibrary`, dépend de `Core`). Auto-inclus par `main.sharpmake.cs` |

### Serveur MCP — `mcp-server/` (Node + TypeScript)
| Fichier | Rôle |
|---|---|
| `src/bridgeClient.ts` | Transport fichier côté serveur : écrit `commands.jsonl`, poll `state.json`, compacte le fichier de commandes, gère `ENGINE_NOT_RUNNING` / `TIMEOUT` |
| `src/index.ts` | Serveur `@modelcontextprotocol/sdk` (stdio). 6 tools : `engine_status`, `list_objects`, `get_transform`, `set_transform`, `spawn_object`, `save_world` |
| `package.json`, `tsconfig.json`, `README.md` | Setup |

## Fichiers existants modifiés (additif uniquement — aucune ligne existante supprimée/altérée)

| Fichier | Diff |
|---|---|
| `sharpmake/vg.solution.sharpmake.cs` | +1 ligne : `conf.AddProject<MCPBridge>(target);` |
| `sharpmake/vg.engine.sharpmake.cs` | +1 ligne : `conf.Defines.Add("VG_ENABLE_MCPBRIDGE");` |
| `sharpmake/vg.data.sharpmake.cs` | +1 ligne : exclusion `mcpbridge` du projet utilitaire `Version` (comme tous les autres modules) |
| `src/engine/Engine.cpp` | +34 lignes, 5 blocs, **tous `#if VG_ENABLE_MCPBRIDGE`** : include, pointeur statique, create+Init (si `getenv("VG_MCP_BRIDGE")`), `Tick()` dans `RunOneFrame`, `Deinit()` |
| `.gitignore` | +1 : `mcp/` |

Retirer la ligne de `vg.engine.sharpmake.cs` + celle de `vg.solution.sharpmake.cs` et régénérer ⇒ le moteur est identique à l'état d'origine (le `#if` neutralise tout le code dans `Engine.cpp`).

---

## Boucle d'intégration

`Engine::RunOneFrame()` → (après les updates, avant `m_editor->RunOneFrame()`) → `g_mcpBridge->Tick()`.
Appelé **chaque frame en mode éditeur, y compris hors Play**. `Tick()` ne fait rien tant que
`mcp/commands.jsonl` n'a pas changé de date de modification (coût quasi nul).

## Protocole (rappel, détail dans `docs/data-contract.md`)

- `mcp/commands.jsonl` : append-only, une commande JSON/ligne `{id, tool, args}`. Écrit par le serveur, lu par le moteur.
- `mcp/state.json` : `{engine:{running,playing}, lastProcessedId, results:[{id,tool,ok,data|error}]}`. Écrit par le moteur (atomique), lu par le serveur.
- Le serveur compacte `commands.jsonl` (supprime les lignes `id <= lastProcessedId`) avant chaque envoi.
- Testé de bout en bout avec un faux moteur (script Node) : list / set / propagation d'erreur ✅.

---

## Piège connu — l'éditeur crashe au démarrage (GTAO)

**Symptôme** : au lancement de `editor.exe` / `vgframework_win64_msvc_dx12_*.exe`, deux assertions puis crash :
`Texture resource "ScreenSpaceAmbient - Editor 0" does not exist in FrameGraph` (`FrameGraph.cpp:164`)
puis `RWTexture "ScreenSpaceAmbient - Editor 0" does not exist in FrameGraph` (`UserPass.hpp:230`).

**Cause** : fonctionnalité amont **en chantier** (commits `9c8c688a SSAO WIP`, `9715a2b1 GTAO`), activée par
défaut dans l'état committé. `LitView::RegisterFrameGraph` (`LitView.hpp:142-144`) n'ajoute la passe qui
**crée** la texture que si `m_lightingMode == Deferred` **et** `GetScreenSpaceAmbient() != None` ; quand la
ressource manque au `Render` alors que la condition est vraie, le `Setup` de la passe SSA n'a pas tourné.

**Sans rapport avec le bridge MCP** : sans la variable d'environnement `VG_MCP_BRIDGE`, `g_mcpBridge` reste
`nullptr` et les trois points d'appel dans `Engine.cpp` sont des branches mortes.

**Contournement appliqué** : `Editor.xml:35` `m_screenSpaceAmbient` **`GTAO` → `None`** (vérifié : l'éditeur
se lance normalement). Pour revenir en arrière : `git checkout -- Editor.xml`.

---

## Phase 5 — mode d'emploi

```powershell
# 1. serveur MCP (une fois)
cd D:\GitHUB_Repo\mcp-server ; npm install ; npm run build

# 2. enregistrer le serveur dans Claude Code (une fois)
# -e et non --env ; le VG_MCP_DIR doit pointer le MÊME dossier que celui du moteur
claude mcp add vgframework -e VG_MCP_DIR=D:/GitHUB_Repo/mcp -- node D:/GitHUB_Repo/mcp-server/dist/index.js
# puis REDÉMARRER Claude Code : une session déjà lancée ne voit pas un serveur ajouté après coup

# 3. lancer l'éditeur AVEC le bridge (à chaque session)
cd D:\GitHUB_Repo ; $env:VG_MCP_BRIDGE = "1" ; .\editor.exe
# log attendu : [MCPBridge] enabled - watching "…/mcp/commands.jsonl"
# le dossier mcp/ doit se créer avec un state.json contenant "running": true
```

4. Poser **à la main** un `data/Prefabs/Box_Base.prefab` dans la scène : `spawn_object` **clone un objet déjà
présent**, il ne sait pas instancier un prefab depuis le disque (hors périmètre V1).
5. Dans Claude Code, en langage naturel : « vérifie que le moteur répond », « liste les objets »,
« cherche Box_Base », puis le scénario grille.

**Rien n'est écrit sur disque tant que `save_world` n'est pas appelé** — recharger la scène annule tout.
Attention : il n'y a **pas de `delete_object`** en V1, le ménage se fait à la main dans l'éditeur.

### Checklist Phase 6

- [x] `docs/engine-analysis.md` complet avec citations
- [x] `docs/architecture.md` (ADR) cohérent avec Phase 0
- [x] `docs/OPEN_QUESTIONS.md` à jour — #1 à #4 tous résolus
- [x] Module bridge compilé sans modifier de fichier existant *(hors 4 câblages additifs validés, tous `#if`-gardés)*
- [x] Serveur MCP fonctionnel, 6 tools **testés contre le moteur réel** via le protocole MCP complet
- [x] Convention de rotation vérifiée contre l'Inspector du moteur (`OPEN_QUESTIONS.md` #4)
- [x] Build Debug **et** Release OK, 0/0, avec et sans `VG_MCP_BRIDGE` défini à l'exécution
- [ ] Scénario « plan de ville » 5×5 joué de bout en bout depuis Claude Code *(nécessite un `Box_Base` posé à la main dans la scène)*

---

## Bugs trouvés pendant la Phase 5

Le passage sur une vraie scène (`Aiguelongue`, 7832 objets) a révélé quatre défauts. **Trois étaient les miens** — aucun n'aurait été vu sur une scène jouet.

| # | Défaut | Où | Correction |
|---|---|---|---|
| 1 | Le heartbeat réécrivait `results` **vide** : tout client plus lent que ~2 s perdait sa réponse | `MCPBridge::writeState` | `results` est toujours republié ; `m_recentResults` stocke des `Json` au lieu de chaînes re-parsées |
| 2 | `list_objects` renvoyait **9,7 Mo** de JSON — inutilisable via MCP | `SceneBridge::listObjects` | modes *browse* (profondeur 1 par défaut) / *search* (`name_contains`), + `parent_id`, `max_depth`, `limit`, `child_count`. **9,7 Mo → 10 Ko** |
| 3 | Signe de rotation inversé : `decompose` lisait la **transposée** de ce qu'écrivait `recompose` | `SceneBridge.cpp` | termes antisymétriques pris en `(r_ij − r_ji)`. ⚠️ Un test à 90° ne peut **pas** détecter ce bug (boîte symétrique) — tester à 45° |
| 4 | `World::AddScene` teste `nullptr == m_activeScene` sur un **tableau** (`World.h:95`) → condition toujours fausse, aucune scène n'est jamais marquée active au chargement d'un monde | `src/engine/World/World.cpp:171` — **amont** | non corrigé (règle d'or). Contourné : le bridge énumère `GetSceneCount`/`GetScene`. Correctif amont si souhaité : `m_activeScene[typeIndex]` |

Le bug 4 passait inaperçu parce que seule la vue Prefab appelle `SetActiveScene` explicitement (`ImGuiPrefabView.hpp:193`).
Loading
Loading