Fiche CAM-B2b-F6b — 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-F6b-IMPL : pilotage de C2, `ScrollDownUp` et `RotateCamera` (B2b-6, partie 2) (2026-10-13) Consignes : `CAM-B2b-FICHES.md` § « Rubriques communes » (l. 23-63), § « F6 » (l. 127-132, `ScrollDownUp`) et § « F7 » (l. 134-136, `RotateCamera` seulement). `DownUp` et la fin de F7 passent à la fiche suivante. SPEC : `CAM-SPEC.md` § « CAM-B2b », ligne B2b-6 (l. 380). Modèle suivi : `CAM-B2b-IMPL-F6a.md` et son ordre économe : créer tous les nœuds, faire une lecture groupée, puis couper chaque lien et le remplacer aussitôt. Contexte : 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 à F6a en mémoire, non enregistrés. Un seul `call_tool` par message. **43 appels MCP** : 3 `get_node_infos`, 1 `find_node_types`, 12 `create_node`, 2 `break_pins`, 18 `connect_pins`, 6 `set_pin_value` et 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` est modifié **en mémoire de l'éditeur seulement**, sans enregistrement (pas de `save_assets` avant F7). Le § 3 le prouve : le MD5 du disque après F6b est celui de l'instantané. - Graphe `/Game/BPs/Room.Room:EventGraph` : - événement `ScrollDownUp` (CE55, x 4896, y 2544) : nœuds neufs en x 5100-5950, y 2150-2350 ; - événement `RotateCamera` (CE19, x 5184, y 5232) : nœuds neufs en x 5400-6250, y 5550-5800. ### 1.1 Lecture avant écriture (`get_node_infos` groupé, 5 nœuds existants) La lecture est conforme aux faits de la fiche : - `K2Node_CustomEvent_55` (`AddEvent|Custom|ScrollDownUp`) : - `then` out 1 → `IfThenElse_10`.execute 0 ; - `Values` out 2 (`Float (double-précision)`) → `CommutativeAssociativeBinaryOperator_2`.0, `CallFunction_26`.2, `CommutativeAssociativeBinaryOperator_20`.0, `_10`.0 et `_22`.0. - `K2Node_CustomEvent_19` (`AddEvent|Custom|RotateCamera`) : - `then` out 1 → `IfThenElse_25`.execute 0 ; - `DeltaSeconds` out 2 (`Float (double-précision)`) → `CommutativeAssociativeBinaryOperator_45`.1, `_12`.1 et `_15`.1. - `K2Node_IfThenElse_10` : execute ← CE55.1 ; Condition ← `CommutativeAssociativeBinaryOperator_17`.0 ; then 0 → `IfThenElse_12`.0 ; else 1 libre. - `K2Node_IfThenElse_25` : execute ← CE19.1 ; Condition ← `CommutativeAssociativeBinaryOperator_19`.0 ; then 0 → `IfThenElse_18`.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, `_96`.1 et `_97`.1. Recherche du Clamp : `find_node_types {type_id_filter:"Clamp"}` sur le graphe. Le résultat flottant est `Math|Flottant|Clamp(Float)`, à côté de `Math|Integer|Clamp(Integer)`, `Math|Integer64|Clamp(Integer64)`, `Math|Flottant| ClampAngle`, `MapRangeClamped` et d'autres. ### 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 | |---|---|---|---|---|---| | ScrollDownUp | **Branche** C2 | `K2Node_IfThenElse_98` | `Utilitaires\|ContrôleDeFlux\|Branche` → idem | execute 0, Condition 1 ; then 0, else 1 | 5100, 2350 | | ScrollDownUp | Obtenir C2Dist | `K2Node_VariableGet_294` | `Variables\|Pardéfaut\|ObtenirC2Dist` → `\|GetC2Dist` | C2Dist out 0 (double) | 5100, 2150 | | ScrollDownUp | Values × 15 | `K2Node_PromotableOperator_31` | `Utilitaires\|Opérateurs\|Multiplier` → `Math\|Flottant\|float*float` | A 0, B 1 ; out 0 (double) | 5300, 2250 | | ScrollDownUp | C2Dist − produit | `K2Node_PromotableOperator_32` | `Utilitaires\|Opérateurs\|Soustraire` → `Math\|Flottant\|float-float` | A 0, B 1 ; out 0 (double) | 5500, 2150 | | ScrollDownUp | Clamp | `K2Node_CallFunction_509` | `Math\|Flottant\|Clamp(Float)` → idem | Value 0, Min 1, Max 2 ; ReturnValue out 0 (double) | 5700, 2200 | | ScrollDownUp | **Définir C2Dist** | `K2Node_VariableSet_69` | `Variables\|Pardéfaut\|DéfinirC2Dist` → `\|SetC2Dist` | execute 0, C2Dist 1 ; then 0, Output_Get 1 | 5950, 2300 | | RotateCamera | **Branche** C2 | `K2Node_IfThenElse_99` | `Utilitaires\|ContrôleDeFlux\|Branche` → idem | execute 0, Condition 1 ; then 0, else 1 | 5450, 5800 | | RotateCamera | Obtenir C2Height | `K2Node_VariableGet_295` | `Variables\|Pardéfaut\|ObtenirC2Height` → `\|GetC2Height` | C2Height out 0 (double) | 5400, 5550 | | RotateCamera | DeltaSeconds × 3 | `K2Node_PromotableOperator_33` | `Utilitaires\|Opérateurs\|Multiplier` → `Math\|Flottant\|float*float` | A 0, B 1 ; out 0 (double) | 5600, 5650 | | RotateCamera | C2Height + produit | `K2Node_PromotableOperator_34` | `Utilitaires\|Opérateurs\|Ajouter` → `Math\|Flottant\|float+float` | A 0, B 1 ; out 0 (double) | 5800, 5550 | | RotateCamera | Clamp | `K2Node_CallFunction_510` | `Math\|Flottant\|Clamp(Float)` → idem | Value 0, Min 1, Max 2 ; ReturnValue out 0 (double) | 6000, 5600 | | RotateCamera | **Définir C2Height** | `K2Node_VariableSet_70` | `Variables\|Pardéfaut\|DéfinirC2Height` → `\|SetC2Height` | execute 0, C2Height 1 ; then 0, Output_Get 1 | 6250, 5700 | À la création, les opérateurs portent le `type_id` trompeur déjà vu en F3 à F6a, avec des broches `Caractère de remplacement` : - `Utilitaires|Gestiondutemps|Seconds*FrameRate` pour PO31 et PO33 ; - `…|FrameNumber-Int` pour PO32 ; - `…|FrameNumber+Int` pour PO34. À la relecture finale, ils sont typés `float*float`, `float-float` et `float+float`, en double précision. À la création, le Clamp a pour défauts Min `0.0` et Max `1.0`, remplacés ensuite (§ 1.5). 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_55.then 1 | IfThenElse_10.execute 0 | | C2 | CustomEvent_19.then 1 | IfThenElse_25.execute 0 | Aucun autre lien n'a été coupé, et rien d'autre n'a été touché dans ces deux événements. ### 1.4 Liens faits (`connect_pins`, 18, tous `{"returnValue":null}`) | # | Sortie | Entrée | Événement | |---|---|---|---| | L1 | CustomEvent_55.then 1 | IfThenElse_98.execute 0 | ScrollDownUp | | L2 | IfThenElse_98.else 1 | IfThenElse_10.execute 0 | ScrollDownUp | | L3 | PromotableOperator_8.0 | IfThenElse_98.Condition 1 | ScrollDownUp | | L4 | CustomEvent_55.Values 2 | PromotableOperator_31.A 0 | ScrollDownUp | | L5 | VariableGet_294.0 (`C2Dist`) | PromotableOperator_32.A 0 | ScrollDownUp | | L6 | PromotableOperator_31.0 | PromotableOperator_32.B 1 | ScrollDownUp | | L7 | PromotableOperator_32.0 | CallFunction_509.Value 0 | ScrollDownUp | | L8 | CallFunction_509.0 | VariableSet_69.C2Dist 1 | ScrollDownUp | | L9 | IfThenElse_98.then 0 | VariableSet_69.execute 0 | ScrollDownUp | | L10 | CustomEvent_19.then 1 | IfThenElse_99.execute 0 | RotateCamera | | L11 | IfThenElse_99.else 1 | IfThenElse_25.execute 0 | RotateCamera | | L12 | PromotableOperator_8.0 | IfThenElse_99.Condition 1 | RotateCamera | | L13 | CustomEvent_19.DeltaSeconds 2 | PromotableOperator_33.A 0 | RotateCamera | | L14 | VariableGet_295.0 (`C2Height`) | PromotableOperator_34.A 0 | RotateCamera | | L15 | PromotableOperator_33.0 | PromotableOperator_34.B 1 | RotateCamera | | L16 | PromotableOperator_34.0 | CallFunction_510.Value 0 | RotateCamera | | L17 | CallFunction_510.0 | VariableSet_70.C2Height 1 | RotateCamera | | L18 | IfThenElse_99.then 0 | VariableSet_70.execute 0 | RotateCamera | Les sorties `VariableSet_69.then` et `VariableSet_70.then` restent libres : elles marquent la fin de l'événement côté C2, comme le schéma de la fiche (identique à F6a). ### 1.5 Valeurs (`set_pin_value`, 6, tous `{"returnValue":null}`, relues) | Broche | Avant (relu à la création) | Après (relu) | |---|---|---| | PromotableOperator_31.B 1 | (neuf, `""`, joker) | `15.0` (`Float (double-précision)`) | | CallFunction_509.Min 1 | `0.0` | `-75.0` | | CallFunction_509.Max 2 | `1.0` | `125.0` | | PromotableOperator_33.B 1 | (neuf, `""`, joker) | `3.0` (`Float (double-précision)`) | | CallFunction_510.Min 1 | `0.0` | `-40.0` | | CallFunction_510.Max 2 | `1.0` | `150.0` | Chaque `set_pin_value` sur un opérateur a été posé juste après la liaison de A, qui type l'opérateur (L4, L13). Les conditions des Branches 98 et 99 gardent leur valeur par défaut `true`, sans effet puisque la broche est liée (L3, L12). ### 1.6 Relecture et chaîne obtenue `get_node_infos` final groupé sur 17 nœuds : les 12 créés, plus CE55, CE19, IfThenElse_10, IfThenElse_25 et PO8. Les 18 liens apparaissent des deux côtés et les 6 valeurs sont relues. Liens non touchés, relus intacts : - CE55.Values → `CommutativeAssociativeBinaryOperator_2`.0, `CallFunction_26`.2, `_20`.0, `_10`.0, `_22`.0 (plus PO31.0) ; - CE19.DeltaSeconds → `CommutativeAssociativeBinaryOperator_45`.1, `_12`.1, `_15`.1 (plus PO33.0) ; - IfThenElse_10 : Condition ← `CommutativeAssociativeBinaryOperator_17`, then → `IfThenElse_12`, else libre ; - IfThenElse_25 : Condition ← `CommutativeAssociativeBinaryOperator_19`, then → `IfThenElse_18`, else libre ; - PO8 : A ← 439, B ← `VariableGet_268`, sortie → `IfThenElse_91`, `_96`, `_97` (plus 98.1 et 99.1). Aucune divergence. Chaîne obtenue : - `ScrollDownUp` (CE55) → Branche 98 (`GetViewTarget(PC) == CamRoom`). - Vrai : `Définir C2Dist` (69) := `Clamp(C2Dist − Values × 15, −75, 125)`, puis fin. - Faux : `IfThenElse_10`, chaîne d'origine inchangée. - `RotateCamera` (CE19) → Branche 99 (même condition). - Vrai : `Définir C2Height` (70) := `Clamp(C2Height + DeltaSeconds × 3, −40, 150)`, puis fin. - Faux : `IfThenElse_25`, chaîne d'origine inchangée. ### 1.7 Compilation `compile_blueprint {blueprint:{refPath:"/Game/BPs/Room.Room"}, warnings_as_errors:true}` a renvoyé `{"returnValue":null}`, sans exception : c'est le critère de la rubrique « Fin de chaque fiche ». `hyper_blueprint_debug_inspect` n'a pas été appelé, car 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, faute du chemin `-abslog`. Pas de `save_assets` : il est réservé à F7. ## 2. Écarts à la SPEC - **Aucun écart de câblage.** La ligne B2b-6 (`CAM-SPEC.md:380`) et la fiche (`CAM-B2b-FICHES.md:132` et `:136`) sont réalisées telles quelles pour `ScrollDownUp` et `RotateCamera`. Seuls les liens CE.then → branche d'origine ont été coupés. - **Formules** : `C2Dist := Clamp(C2Dist − Values × 15, −75, 125)` et `C2Height := Clamp(C2Height + D × 3, −40, 150)`. Les opérandes ne sont pas inversés : A de PO32 = C2Dist, B = produit, relu. - **Typage** : `Values` et `DeltaSeconds` sont en double précision, comme `Clamp(Float)` (Value, Min, Max et sortie en double) : aucune conversion implicite. - **Nœuds par événement** : un `Obtenir` et un `Clamp` distincts dans chacun, comme le prescrit la fiche (« (N) »). PO8 est désormais partagé par cinq Branches (91, 96, 97, 98, 99). - **Garde `Accessed None`** : aucune `IsValid` ajoutée, car ni la fiche ni la SPEC n'en demandent, et 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 `ScrollDownUp` et de `RotateCamera`, ce qui n'était pas le cas avant. Même réserve qu'en F6a (§ 5, points 3 et 4). - **Comportement** : identique à l'ancien quand C2 n'est pas la vue (sortie `else` → 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 proches : nœuds `LeftRight` de F6a (x 5250-5850, y 2750-2950), `IfThenElse_25` (5888, 5328). Sans effet sur le graphe. ## 3. Instantané Fait par l'orchestrateur avant F1 (non refait, consigne). Manifeste relu et disque mesuré après F6b : ``` 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:06:13+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 à F6b ; - 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 - `DownUp` (CE8 → IfThenElse_8, `Clamp(C2Height + AxisValue × 3, −40, 150)`) : renvoyé à la fiche suivante (F7). - Fin de F7 : relecture de CE1, CE4, CE8, CE19, CE55, 459, 467, 474, 61 et 62, compilation stricte, puis `save_assets` de `/Game/BPs/Room` seul. Hors périmètre. - `hyper_blueprint_debug_inspect` et journal de l'éditeur : non consultés (§ 1.7). - `save_assets` : interdit avant F7. F1 à F6b 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** : `ScrollDownUp` et `RotateCamera` prennent `else` → `IfThenElse_10` / `IfThenElse_25`, avec leurs chaînes d'origine inchangées. Établi par le câblage ; à confirmer en jeu (zoom et tangage de l'ancienne caméra identiques). 2. **Vue C2** : - `ScrollDownUp` retire `Values × 15` à `C2Dist`, borné à [−75, 125] (distance 100 à 300 cm selon la SPEC) ; - `RotateCamera` ajoute `DeltaSeconds × 3` à `C2Height`, borné à [−40, 150]. L'effet passe par `ComputeC2Target` (F3) au prochain appel. Signes et gains sont à juger à l'essai (SPEC : « ajustables à l'essai »). 3. **`LogStats`** : aucun `Accessed None` nouveau sur `CallFunction_439` / `VariableGet_262`. PO8 est maintenant évalué à chaque `ScrollDownUp` et à chaque `RotateCamera` ; ce dernier est probablement appelé à chaque image pendant la rotation (estimé, appelant non lu). 4. **Cas limite (estimé, déjà noté en F6a)** : si `GetViewTarget` rend `None` et que `CamRoom` est aussi `None`, PO8 vaut `true` (None == None), et ces événements écrivent alors `C2Dist` / `C2Height` au lieu de piloter l'ancienne caméra. 5. **`RotateCamera.DeltaSeconds`** est un delta de tangage selon la fiche, pas un temps (nom hérité). Si l'appelant passe une valeur par image, le gain ×3 dépend de la fréquence d'image. Estimé, à juger à l'essai. 6. **V5 (`CAM-SPEC.md:382`)** pour ces deux événements : `then` → nouvelle branche (98, 99) ; `else` → branche d'origine (10, 25). 7. **Valeurs sérialisées** après `save_assets` (F7), à relire par dump (`LPDumpFunction`) : PO31.B = 15.0 ; 509 Min −75.0, Max 125.0 ; PO33.B = 3.0 ; 510 Min −40.0, Max 150.0. 8. **Compilation stricte** jugée sur la seule absence d'exception ; à recontrôler en F7 et par `CompileAllBlueprints`. ## 6. Fichiers à commiter - `A Docs/Migration/etat/CAM/CAM-B2b-IMPL-F6b.md` (dépôt du jeu, branche `ue5.8`). - `Content/BPs/Room.uasset` : rien à commiter pour F6b (non enregistré) ; il le sera après F7 et la VAL. Tours : 48 / 60 (ce Write et la remise du compte rendu compris ; 43 appels MCP, un par message) ## Constats - `Math|Flottant|Clamp(Float)` crée un `K2Node_CallFunction`. Broches : Value 0 (défaut 0.0), Min 1 (défaut 0.0), Max 2 (défaut 1.0), ReturnValue out 0, toutes en `Float (double-précision)` dès la création (pas de joker). - Correspondances relevées : - `Variables|Pardéfaut|ObtenirC2Dist` → `|GetC2Dist` ; `ObtenirC2Height` → `|GetC2Height` ; - `DéfinirC2Dist` → `|SetC2Dist` ; `DéfinirC2Height` → `|SetC2Height` ; broches comme `SetC2Yaw` (execute 0, valeur 1 ; then 0, Output_Get 1). - `Utilitaires|Opérateurs|Soustraire` naît en `Utilitaires|Gestiondutemps|FrameNumber-Int`. Lier A à un double le type en `Math|Flottant|float-float` (A − B, A index 0). - `DownUp` (F7), dans le même schéma : 6 créations, 1 coupure, 9 liaisons, 3 valeurs, soit 19 appels plus 1 lecture groupée. Le Clamp se crée directement par `Math|Flottant|Clamp(Float)`, sans `find_node_types`. - À verser : lp-mcp § 3 — `type_id` `Math|Flottant|Clamp(Float)` et index Value 0 / Min 1 / Max 2 / sortie 0 ; `Soustraire` naît en `FrameNumber-Int` puis devient `float-float` à la liaison de A.