Fiche CAM-B2b-F6a — Niveau N3 — Modèle prévu opus | utilisé claude-opus-5-5 (Opus 5.5) — Agent lp-mcp-bp — Skills sk-protocol, sk-change-safety, lp-mcp, lp-lot # CAM-B2b-F6a-IMPL : pilotage de C2, `CircleCamera` et `LeftRight` (B2b-6, partie 1) (2026-10-13) Consignes : `CAM-B2b-FICHES.md` § « Rubriques communes » (l. 23-63) et § « F6 » (l. 127-132), réduit par la fiche F6a à `CircleCamera` et `LeftRight` (`ScrollDownUp` passe en F6b). SPEC : `CAM-SPEC.md` § « CAM-B2b », ligne B2b-6 (l. 380). BUILD-GO / UE-GO de Big Boss B2b du 2026-10-13 (cité par l'orchestrateur). Éditeur headless PID 10708, port 8000, connexion `unreal-mcp` partagée (MCPA-T1). Room chargé, F1 à F5 en mémoire, non enregistrés. Un seul `call_tool` par message. **31 appels MCP** : 9 `create_node`, 2 `break_pins`, 15 `connect_pins`, 1 `set_pin_value`, 3 `get_node_infos`, 1 `compile_blueprint`. Aucun `delete_node`, `save_assets`, `write_graph_dsl` ni `ProgrammaticToolset`. Aucune écriture par HyperAIStudio. ## 1. Fichiers modifiés - `Content/BPs/Room.uasset` : modifié **en mémoire de l'éditeur seulement**, non enregistré (pas de `save_assets` avant F7). Preuve au § 3 : le MD5 du disque après F6a est celui de l'instantané. - Graphe `/Game/BPs/Room.Room:EventGraph` : événement `CircleCamera` (CE1, x 5296, y 4688) et événement `LeftRight` (CE4, x 5072, y 3168). Nœuds neufs en x 5650-6050, y 4250-4450 (CircleCamera) et x 5250-5850, y 2750-2950 (LeftRight). ### 1.1 Lecture avant écriture (`get_node_infos` groupé, 5 nœuds existants) Lecture conforme aux faits de la fiche : - `K2Node_CustomEvent_1` (`AddEvent|Custom|CircleCamera`) : `then` out 1 → `IfThenElse_26`.execute 0 ; `DeltaSeconds` out 2 (`Float (double-précision)`) → `CommutativeAssociativeBinaryOperator_26`.1, `_16`.1, `_27`.1. - `K2Node_CustomEvent_4` (`AddEvent|Custom|LeftRight`) : `then` out 1 → `IfThenElse_9`.execute 0 ; `AxisValue` out 2 (`Float (double-précision)`) → `CommutativeAssociativeBinaryOperator_47`.0, `_13`.0, `_24`.0. - `K2Node_IfThenElse_26` : execute ← CE1.1 ; Condition ← `CommutativeAssociativeBinaryOperator_19`.0 ; then 0 → `IfThenElse_19`.0 ; else 1 libre. - `K2Node_IfThenElse_9` : execute ← CE4.1 ; Condition ← `CommutativeAssociativeBinaryOperator_17`.0 ; then 0 → `IfThenElse_14`.0 ; else 1 libre. - `K2Node_PromotableOperator_8` (`Utilitaires|Equal(Object)`) : A ← `CallFunction_439`.0, B ← `VariableGet_268`.0 ; out 0 (`Booléen`) → `IfThenElse_91`.1. Les deux paramètres d'événement sont en **double précision** (la fiche prévoyait peut-être de la simple) : aucune conversion implicite n'est nécessaire. ### 1.2 Nœuds créés (préfixe `refPath` : `/Game/BPs/Room.Room:EventGraph.`) | Événement | Rôle | `refPath` (suffixe) | `type_id` demandé → relu | Broches relues | Position | |---|---|---|---|---|---| | CircleCamera | **Branche** C2 | `K2Node_IfThenElse_96` | `Utilitaires\|ContrôleDeFlux\|Branche` → idem | execute 0, Condition 1 ; then 0, else 1 | 5650, 4450 | | CircleCamera | Obtenir C2Yaw | `K2Node_VariableGet_292` | `Variables\|Pardéfaut\|ObtenirC2Yaw` → `\|GetC2Yaw` | C2Yaw out 0 (double) | 5650, 4250 | | CircleCamera | C2Yaw + DeltaSeconds | `K2Node_PromotableOperator_28` | `Utilitaires\|Opérateurs\|Ajouter` → `Math\|Flottant\|float+float` | A 0, B 1 ; out 0 (double) | 5850, 4250 | | CircleCamera | **Définir C2Yaw** | `K2Node_VariableSet_67` | `Variables\|Pardéfaut\|DéfinirC2Yaw` → `\|SetC2Yaw` | execute 0, C2Yaw 1 ; then 0, Output_Get 1 | 6050, 4400 | | LeftRight | **Branche** C2 | `K2Node_IfThenElse_97` | `Utilitaires\|ContrôleDeFlux\|Branche` → idem | execute 0, Condition 1 ; then 0, else 1 | 5250, 2950 | | LeftRight | Obtenir C2Yaw | `K2Node_VariableGet_293` | `Variables\|Pardéfaut\|ObtenirC2Yaw` → `\|GetC2Yaw` | C2Yaw out 0 (double) | 5250, 2750 | | LeftRight | AxisValue × 2 | `K2Node_PromotableOperator_29` | `Utilitaires\|Opérateurs\|Multiplier` → `Math\|Flottant\|float*float` | A 0, B 1 ; out 0 (double) | 5450, 2850 | | LeftRight | C2Yaw + produit | `K2Node_PromotableOperator_30` | `Utilitaires\|Opérateurs\|Ajouter` → `Math\|Flottant\|float+float` | A 0, B 1 ; out 0 (double) | 5650, 2750 | | LeftRight | **Définir C2Yaw** | `K2Node_VariableSet_68` | `Variables\|Pardéfaut\|DéfinirC2Yaw` → `\|SetC2Yaw` | execute 0, C2Yaw 1 ; then 0, Output_Get 1 | 5850, 2900 | À la création, les opérateurs portent le `type_id` trompeur déjà vu en F3 à F5 : `Utilitaires|Gestiondutemps| FrameNumber+Int` pour PO28 et PO30, `…|Seconds*FrameRate` pour PO29, avec des broches `Caractère de remplacement`. À la relecture finale, ils sont typés `float+float` / `float*float` en double précision. PO8 est réutilisé sans copie (nœud pur). ### 1.3 Liens coupés (`break_pins`, 2, tous `{"returnValue":null}`) | # | Sortie | Entrée | |---|---|---| | C1 | CustomEvent_1.then 1 | IfThenElse_26.execute 0 | | C2 | CustomEvent_4.then 1 | IfThenElse_9.execute 0 | Aucun autre lien coupé ; dans ces deux événements, rien d'autre n'a été touché. ### 1.4 Liens faits (`connect_pins`, 15, tous `{"returnValue":null}`) | # | Sortie | Entrée | Événement | |---|---|---|---| | L1 | CustomEvent_1.then 1 | IfThenElse_96.execute 0 | CircleCamera | | L2 | IfThenElse_96.else 1 | IfThenElse_26.execute 0 | CircleCamera | | L3 | PromotableOperator_8.0 | IfThenElse_96.Condition 1 | CircleCamera | | L4 | VariableGet_292.0 (`C2Yaw`) | PromotableOperator_28.A 0 | CircleCamera | | L5 | CustomEvent_1.DeltaSeconds 2 | PromotableOperator_28.B 1 | CircleCamera | | L6 | PromotableOperator_28.0 | VariableSet_67.C2Yaw 1 | CircleCamera | | L7 | IfThenElse_96.then 0 | VariableSet_67.execute 0 | CircleCamera | | L8 | CustomEvent_4.then 1 | IfThenElse_97.execute 0 | LeftRight | | L9 | IfThenElse_97.else 1 | IfThenElse_9.execute 0 | LeftRight | | L10 | PromotableOperator_8.0 | IfThenElse_97.Condition 1 | LeftRight | | L11 | CustomEvent_4.AxisValue 2 | PromotableOperator_29.A 0 | LeftRight | | L12 | VariableGet_293.0 (`C2Yaw`) | PromotableOperator_30.A 0 | LeftRight | | L13 | PromotableOperator_29.0 | PromotableOperator_30.B 1 | LeftRight | | L14 | PromotableOperator_30.0 | VariableSet_68.C2Yaw 1 | LeftRight | | L15 | IfThenElse_97.then 0 | VariableSet_68.execute 0 | LeftRight | `VariableSet_67.then` et `VariableSet_68.then` : laissés libres (fin de l'événement côté C2, comme le prescrit la fiche). ### 1.5 Valeurs (`set_pin_value`, 1, `{"returnValue":null}`, relue) | Broche | Avant (relu à la création) | Après (relu) | |---|---|---| | PromotableOperator_29.B 1 | (neuf, `""`, joker) | `2.0` (`Float (double-précision)`) | Le `set_pin_value` a été posé juste après L11, la liaison de A qui type l'opérateur. Les conditions des Branches 96 et 97 gardent la valeur par défaut `true`, sans effet puisque la broche est liée (L3, L10). ### 1.6 Relecture et chaîne obtenue `get_node_infos` final groupé sur 15 nœuds : les 9 créés, plus CE1, CE4, IfThenElse_26, IfThenElse_9, PO8 et `CallFunction_439` (amont de PO8, lu pour la garde). Les 15 liens apparaissent des deux côtés ; la valeur 2.0 est relue. Liens non touchés, relus intacts : - CE1.DeltaSeconds → `CommutativeAssociativeBinaryOperator_26`, `_16`, `_27` (plus PO28) ; - CE4.AxisValue → `CommutativeAssociativeBinaryOperator_47`, `_13`, `_24` (plus PO29) ; - IfThenElse_26 : Condition ← `CommutativeAssociativeBinaryOperator_19`, then → `IfThenElse_19`, else libre ; - IfThenElse_9 : Condition ← `CommutativeAssociativeBinaryOperator_17`, then → `IfThenElse_14`, else libre ; - PO8 : A ← 439, B ← `VariableGet_268`, sortie → `IfThenElse_91`.1 (plus 96.1 et 97.1) ; - `CallFunction_439` (`Pion|GetViewTarget`) : self ← `VariableGet_262` (`Controller`), sortie → `PromotableOperator_0`.0 et PO8.0. Aucune divergence. Chaîne obtenue : - `CircleCamera` (CE1) → Branche 96 (`GetViewTarget(PC) == CamRoom`). Si vrai : `Définir C2Yaw` (67) := `C2Yaw + DeltaSeconds`, puis fin. Si faux : `IfThenElse_26`, chaîne d'origine inchangée. - `LeftRight` (CE4) → Branche 97 (même condition). Si vrai : `Définir C2Yaw` (68) := `C2Yaw + AxisValue × 2`, puis fin. Si faux : `IfThenElse_9`, chaîne d'origine inchangée. ### 1.7 Compilation `compile_blueprint {blueprint:{refPath:"/Game/BPs/Room.Room"}, warnings_as_errors:true}` → `{"returnValue":null}`, aucune exception (critère de la rubrique « Fin de chaque fiche »). `hyper_blueprint_debug_inspect` n'a pas été appelé : il était absent du serveur le 2026-10-10 (mémoire `unreal-mcp-reconnect`, non revérifié). Le journal de l'éditeur n'a pas été lu : son chemin `-abslog` n'a pas été fourni. Pas de `save_assets` (réservé à F7). ## 2. Écarts à la SPEC - Aucun écart de câblage : la ligne B2b-6 (`CAM-SPEC.md:380`), pour `CircleCamera` et `LeftRight`, et la fiche F6 (`CAM-B2b-FICHES.md:127-131`) sont réalisées telles quelles. Seuls les liens CE.then → branche d'origine ont été coupés. - Fait différent de la fiche, sans effet : `DeltaSeconds` et `AxisValue` sont en double précision, pas en simple. Il n'y a donc aucune conversion implicite. - Deux `Obtenir C2Yaw` distincts (292, 293), un par événement, comme le prescrit la fiche (« (N) Obtenir C2Yaw ») ; PO8 est partagé par trois Branches (91, 96, 97). - Garde `Accessed None` : aucune `IsValid` ajoutée, car ni la fiche ni la SPEC n'en demandent. Les nœuds neufs ne lisent que des flottants. En revanche, PO8 (→ 439 `GetViewTarget`, self ← `VariableGet_262` ; → `VariableGet_268` `CamRoom`) est **désormais évalué à chaque appel** de `CircleCamera` et de `LeftRight`, ce qui n'était pas le cas avant. Si la cible de 262 (le contrôleur) est nulle à ce moment, un `Accessed None` apparaît là où il n'y en avait pas. Voir le § 5, points 3 et 4. - Comportement : identique à l'ancien quand C2 n'est pas la vue (sortie `else` vers la branche d'origine). Quand C2 est la vue, la chaîne d'origine **n'est plus exécutée** : c'est le changement voulu par la SPEC (« vrai → réglage ; faux → branche existante »). - Positions des nœuds : choisies près des événements (cosmétique, non prescrit). Elles peuvent chevaucher visuellement des nœuds existants proches, sans effet sur le graphe. ## 3. Instantané Fait par l'orchestrateur avant F1 (non refait, consigne). Manifeste relu et disque mesuré après F6a : ``` date -Iseconds; md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/Room.uasset"; grep -i "Room.uasset" "/d/Game/LifePlay_Backup_5.5/CAM-B2b/manifest.csv" 2026-10-13T16:01:23+02:00 a5fa970e821dc2d0d9bf220fd43b9de6 */d/Game/LifePlay-master 5.8/Content/BPs/Room.uasset "Content\BPs\Room.uasset","True","6060745","A5FA970E821DC2D0D9BF220FD43B9DE6","2026-10-13T15:25:45" ``` Le MD5 du disque est celui de l'instantané : rien n'a été enregistré. Retour arrière avant F7 : fermer l'éditeur sans enregistrer, ce qui perd F1 à F6a. Après F7 : `Tools\Restore.ps1 -Lot CAM-B2b -Only Content\BPs\Room.uasset`, éditeur fermé, sur décision de l'orchestrateur (jamais lancé ici). ## 4. Ce qui n'a pas été fait - `ScrollDownUp` (CE55 → IfThenElse_10, `Clamp` de `C2Dist`) : renvoyé à F6b par la fiche. - F7 (`RotateCamera`, `DownUp`, relecture finale, enregistrement) : hors périmètre. - `hyper_blueprint_debug_inspect`, journal de l'éditeur : non consultés (§ 1.7). - `save_assets` : interdit avant F7. F1 à F6a vivent en mémoire de l'éditeur PID 10708 : une fermeture ou un plantage avant F7 les perd, l'instantané restant intact. - Comportement à l'exécution : non mesuré (aucune PIE, aucun build) ; seulement déduit du câblage. - Preuve en jeu (build empaqueté + `LogStats`) : hors rôle. ## 5. Points à confirmer en VAL 1. Vue autre que C2 : `CircleCamera` et `LeftRight` prennent `else` → `IfThenElse_26` / `IfThenElse_9`, chaînes d'origine inchangées. Établi par le câblage ; à confirmer en jeu (pilotage de l'ancienne caméra identique). 2. Vue C2 : `CircleCamera` ajoute `DeltaSeconds` à `C2Yaw`, et `LeftRight` ajoute `AxisValue × 2`. L'effet sur C2 passe par `Bdir` (F3, 497) au prochain `ComputeC2Target`. Signes et gains à juger à l'essai (SPEC : « ajustables à l'essai »). 3. `LogStats` : aucun `Accessed None` nouveau sur `CallFunction_439` / `VariableGet_262`. PO8 est maintenant évalué à chaque `LeftRight`, événement d'axe probablement appelé à chaque image (estimé, non lu), et à chaque `CircleCamera`. 4. Cas limite (estimé) : si `GetViewTarget` rend `None` et que `CamRoom` est aussi `None` (avant sa création), PO8 vaut `true` (None == None). Les deux événements écrivent alors `C2Yaw` au lieu de piloter l'ancienne caméra. À vérifier : `CamRoom` existe-t-elle toujours avant le premier appel possible de ces événements ? 5. `C2Yaw` n'est pas borné (la SPEC ne le borne pas) : il croît sans fin à force de tourner. `RotateVectorAroundAxis` accepte tout angle ; la précision double suffit (estimé). 6. V5 (`CAM-SPEC.md:382`) pour ces deux événements : `then` → nouvelle branche (96, 97) ; `else` → branche d'origine (26, 9). 7. Valeur sérialisée après `save_assets` (F7), par dump (`LPDumpFunction`) : `PO29.B = 2.0`. 8. Compilation stricte jugée sur l'absence d'exception seulement ; à recontrôler en F7 et par `CompileAllBlueprints`. ## 6. Fichiers à commiter - `A Docs/Migration/etat/CAM/CAM-B2b-IMPL-F6a.md` (dépôt du jeu, branche `ue5.8`). - `Content/BPs/Room.uasset` : rien à commiter pour F6a (non enregistré) ; il le sera après F7 et la VAL. Tours : 38 / 55 (ce Write et la remise du compte rendu compris ; 31 appels MCP, un par message) ## Constats - `DeltaSeconds` (CE1) et `AxisValue` (CE4) sont en `Float (double-précision)` : relier A d'un `Ajouter` ou d'un `Multiplier` à ces broches le type directement en `float+float` / `float*float`, sans conversion. - `Variables|Pardéfaut|ObtenirC2Yaw` → `|GetC2Yaw` (sortie 0) ; `DéfinirC2Yaw` → `|SetC2Yaw` (execute 0, C2Yaw 1 ; then 0, Output_Get 1). - `CallFunction_439` = `Pion|GetViewTarget`, self ← `VariableGet_262` (`Controller`), sortie aussi vers `PromotableOperator_0`. - Pour F6b et F7, le même schéma coûte 2 + 4 à 5 créations + 1 lecture + 1 coupure + 7 à 9 liaisons + 0 à 3 valeurs par événement. Ordre économe : créer tous les nœuds, faire une lecture groupée, puis couper chaque lien et le remplacer aussitôt.