18 KiB
Fiche CAM-B2d — 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-B2d-IMPL : C2 sur la rotation de contrôle (B2d-1 à B2d-4) (2026-10-13)
Consignes : CAM-B2d-FICHE.md § « Consignes de l'agent » ; rubriques communes de CAM-B2b-FICHES.md (l. 23-63).
SPEC : CAM-SPEC.md § « CAM-B2d » (l. 384-393). UE-GO de Big Boss pour le passage Room B2d + B2e (2026-10-13, cité
par l'orchestrateur). Éditeur headless PID 22060, port 8000, connexion unreal-mcp partagée (MCPA-T1) ; Room chargé
par la session (état B2b F1-F7 enregistré). Consigne de l'orchestrateur, différente de la fiche : pas de
save_assets en B2d, B2e enregistre Room dans le même éditeur.
Un seul call_tool par message. 53 appels MCP : 4 find_node_types, 13 create_node (dont 1 en erreur voulue,
§ 2 E1), 6 delete_node, 21 connect_pins, 1 break_pins, 5 get_node_infos (dont 1 en erreur), 2 find_nodes,
1 compile_blueprint. Aucun set_pin_value, 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é (consigne : B2e enregistre). Le MD5 du disque est celui de l'instantané (§ 3).- Graphe
/Game/BPs/Room.Room:EventGraph:ComputeC2Target(zone de 454, x 34450 y 1850),CircleCamera(96, x 5650 y 4450),LeftRight(97, x 5250 y 2950),PlaceC2(CE48, x 34000 y 3000).
1.1 Identifiants trouvés (find_node_types)
ControlRotation, contexte = sortie 0 deVariableGet_262:Pion|SetControlRotation,Pion|GetControlRotation.Rotator:Math|Rotateur|BreakRotator,Math|Rotateur|MakeRotator.GetForwardVector:Transformation|GetForwardVector,Math|Vecteur|GetForwardVector(version rotateur, relue : entréeInRotRotator).GetActorRotation:Transformation|GetActorRotation.
1.2 Lecture avant câblage (get_node_infos groupé, 11 créés + 15 existants), conforme à la fiche
- 453
Transformation|GetActorForwardVector: self ← 261.0 ; out 0 → 454.A 0. 454Normalize2D(Vector): A 0, Tolerance 1 ; out 0 → PO17.A. - 262
|GetPC(out 0PC Référence d'objet, self ←VariableGet_257GI) → 394.1, 434.1, 439.0, 466.1. - 261
|GetPlayerCharacter(out 0Person Référence d'objet) → 264, 265, 266, 434.2, PO0.1, 451, 452, 453, MakeArray_5. - 96 : execute ← CE1.1 ; Condition ← PO8 ; then 0 → 67.0 ; else 1 → IfThenElse_26. PO28
float+float: A ← 292, B ← CE1.2 (DeltaSeconds) ; out → 67.1. 67|SetC2Yaw: then libre. - 97 : execute ← CE4.1 ; Condition ← PO8 ; then 0 → 68.0 ; else 1 → IfThenElse_9. PO30 : A ← 293, B ← PO29 ; out → 68.1. 68 : then libre.
- CE48
AddEvent|Custom|PlaceC2: then (out 1) → 461.execute 0. 461|EnsureCamRoom: then →CallFunction_462. K2Node_CallFunction_521: nœud existant,MakeRotator(8240, 5872 ; sortie →CallFunction_90.2, Pitch ←CommutativeAssociativeBinaryOperator_12, Yaw ←CallFunction_84.2). Lu comme sonde (mon 3eMakeRotatora reçu le n° 522) ; non touché.
1.3 Nœuds créés (préfixe refPath : /Game/BPs/Room.Room:EventGraph.)
| Rôle | refPath (suffixe) |
type_id |
Broches relues (index) | Position |
|---|---|---|---|---|
CR = GetControlRotation (Controller) |
K2Node_CallFunction_524 |
Pion|GetControlRotation + declaring_class (§ 2 E1) |
self in 0 (Controller Référence d'objet) ; ReturnValue out 0 (Rotator) |
33700, 2200 |
GetForwardVector(CR) |
K2Node_CallFunction_513 |
Math|Vecteur|GetForwardVector |
InRot in 0 ; ReturnValue out 0 (Vector) | 34000, 2050 |
BR = BreakRotator(CR) (partagé) |
K2Node_CallFunction_514 |
Math|Rotateur|BreakRotator |
InRot in 0 ; Roll out 0, Pitch out 1, Yaw out 2 (simple précision) | 34000, 1900 |
MakeRotator (CircleCamera) |
K2Node_CallFunction_515 |
Math|Rotateur|MakeRotator |
Roll in 0, Pitch in 1, Yaw in 2 (simple précision) ; out 0 | 6050, 4250 |
SetControlRotation (CircleCamera) |
K2Node_CallFunction_516 |
Pion|SetControlRotation |
execute in 0, self in 1 (Controller), NewRotation in 2 (Rotator par référence) ; then out 0 |
6300, 4400 |
MakeRotator (LeftRight) |
K2Node_CallFunction_517 |
Math|Rotateur|MakeRotator |
idem 515 | 5900, 2700 |
SetControlRotation (LeftRight) |
K2Node_CallFunction_518 |
Pion|SetControlRotation |
idem 516 | 6150, 2900 |
GetActorRotation(MC) (PlaceC2) |
K2Node_CallFunction_519 |
Transformation|GetActorRotation |
self in 0 (Acteur) ; ReturnValue out 0 (Rotator) |
37000, 3000 |
BreakRotator (PlaceC2) |
K2Node_CallFunction_520 |
Math|Rotateur|BreakRotator |
idem 514 | 37250, 3000 |
MakeRotator (PlaceC2) |
K2Node_CallFunction_522 |
Math|Rotateur|MakeRotator |
idem 515 | 37500, 3000 |
SetControlRotation (PlaceC2) |
K2Node_CallFunction_523 |
Pion|SetControlRotation |
idem 516 | 37750, 3100 |
Roll et Pitch des trois MakeRotator : défaut 0.0, non liés (SPEC : MakeRotator(0, 0, Yaw)).
1.4 Nœuds supprimés (delete_node, 6, tous {"returnValue":null})
| Nœud | Rôle | Ligne SPEC |
|---|---|---|
K2Node_CallFunction_512 |
mon premier GetControlRotation, version Pawn (self Pion), jamais relié |
§ 2 E1 |
K2Node_CallFunction_453 |
GetActorForwardVector(MC) |
B2d-1 |
K2Node_VariableSet_67 puis K2Node_VariableGet_292 |
Définir C2Yaw / Obtenir C2Yaw (CircleCamera) |
B2d-2 |
K2Node_VariableSet_68 puis K2Node_VariableGet_293 |
idem (LeftRight) | B2d-3 |
1.5 Liens coupés
- Par
break_pins(1) : C1CustomEvent_48.then 1→CallFunction_461.execute 0(B2d-4). - Par
delete_node(§ 2 E2) : 453.0 → 454.A 0 et 261.0 → 453.self ; 292.0 → PO28.A 0 ; 96.then 0 → 67.execute 0 et PO28.0 → 67.C2Yaw 1 ; 293.0 → PO30.A 0 ; 97.then 0 → 68.execute 0 et PO30.0 → 68.C2Yaw 1.
1.6 Liens faits (connect_pins, 21, tous {"returnValue":null})
| # | Sortie | Entrée | Ligne SPEC |
|---|---|---|---|
| L1 | VariableGet_262.0 (PC) | CallFunction_524.self 0 | B2d-1 |
| L2 | CallFunction_524.0 | CallFunction_513.InRot 0 | B2d-1 |
| L3 | CallFunction_513.0 | CallFunction_454.A 0 | B2d-1 |
| L4 | CallFunction_524.0 | CallFunction_514.InRot 0 | BR |
| L5 | CallFunction_514.Yaw 2 | PromotableOperator_28.A 0 | B2d-2 |
| L6 | PromotableOperator_28.0 | CallFunction_515.Yaw 2 | B2d-2 |
| L7 | CallFunction_515.0 | CallFunction_516.NewRotation 2 | B2d-2 |
| L8 | VariableGet_262.0 | CallFunction_516.self 1 | B2d-2 |
| L9 | IfThenElse_96.then 0 | CallFunction_516.execute 0 | B2d-2 |
| L10 | CallFunction_514.Yaw 2 | PromotableOperator_30.A 0 | B2d-3 |
| L11 | PromotableOperator_30.0 | CallFunction_517.Yaw 2 | B2d-3 |
| L12 | CallFunction_517.0 | CallFunction_518.NewRotation 2 | B2d-3 |
| L13 | VariableGet_262.0 | CallFunction_518.self 1 | B2d-3 |
| L14 | IfThenElse_97.then 0 | CallFunction_518.execute 0 | B2d-3 |
| L15 | VariableGet_261.0 (MC) | CallFunction_519.self 0 | B2d-4 |
| L16 | CallFunction_519.0 | CallFunction_520.InRot 0 | B2d-4 |
| L17 | CallFunction_520.Yaw 2 | CallFunction_522.Yaw 2 | B2d-4 |
| L18 | CallFunction_522.0 | CallFunction_523.NewRotation 2 | B2d-4 |
| L19 | VariableGet_262.0 | CallFunction_523.self 1 | B2d-4 |
| L20 | CustomEvent_48.then 1 | CallFunction_523.execute 0 | B2d-4 |
| L21 | CallFunction_523.then 0 | CallFunction_461.execute 0 | B2d-4 |
516.then et 518.then laissés libres (fin de l'événement côté C2, comme 67 et 68 avant).
1.7 Relecture (get_node_infos groupé final, 20 nœuds : 11 créés + 454, PO28, PO30, 96, 97, CE48, 461, 262, 261)
Les 21 liens apparaissent des deux côtés. 454.A ← 513 seul ; PO28 : A ← 514.Yaw, B ← CE1.DeltaSeconds, out → 515
seul (float+float, double précision conservée) ; PO30 : A ← 514.Yaw, B ← PO29, out → 517 seul ; 96 : then → 516
seul, else → IfThenElse_26 (inchangé), Condition ← PO8 ; 97 : then → 518 seul, else → IfThenElse_9 (inchangé),
Condition ← PO8 ; CE48.then → 523 seul ; 461 : execute ← 523, then → 462 (inchangé) ; 262 → 394, 434, 439, 466
(inchangés) + 524, 516, 518, 523 ; 261 → liens d'origine sans 453, + 519. Aucune divergence.
Absence : find_nodes {title:"Forward"} → [K2Node_CallFunction_513] seul (témoin positif : la recherche par
sous-chaîne marche ; 453 n'est plus dans le graphe). get_node_infos sur 512 → Parameter error: …K2Node_CallFunction_512 is not valid EdGraphNode. Voir § 2 E5 pour 453 et § 5 pour 67, 68, 292, 293.
Chaîne obtenue :
ComputeC2Target: direction arrière =Normalize2D(GetForwardVector(PC.GetControlRotation()))(au lieu deGetActorForwardVector(MC)), puis PO17 (× −1) → 497 (C2Yaw, désormais toujours 0) → PO3, inchangés.CircleCameraen C2 :PC.SetControlRotation(0, 0, ControlYaw + DeltaSeconds), fin. Hors C2 :IfThenElse_26.LeftRighten C2 :PC.SetControlRotation(0, 0, ControlYaw + AxisValue × 2), fin. Hors C2 :IfThenElse_9.PlaceC2:PC.SetControlRotation(0, 0, MC.GetActorRotation().Yaw)→EnsureCamRoom(461) → suite inchangée.
1.8 Compilation
compile_blueprint {blueprint:{refPath:"/Game/BPs/Room.Room"}, warnings_as_errors:true} → {"returnValue":null},
aucune exception. hyper_blueprint_debug_inspect non appelé : il était absent du serveur le 2026-10-10 (mémoire
unreal-mcp-reconnect), non revérifié. Journal de l'éditeur non lu : chemin -abslog non fourni. Pas de save_assets
(consigne de l'orchestrateur).
2. Écarts à la SPEC
- E1 (identifiant, résolu ; méthode hors fiche) :
create_nodeavecPion|GetControlRotationa créé la version Pawn (512, selfPion Référence d'objet), qui n'accepte pas 262 (PC Référence d'objet). Lefind_node_typesavec contexte ne donnait qu'un seultype_id;create_nodene prend pas ce contexte. Un appelcreate_nodevolontairement mal formé (pos:"schema") a renvoyéFunction input params Json - {…,"declaring_class":null}. Ce retour révèle un paramètre caché.create_node {…, declaring_class:"/Script/Engine.Controller"}a créé 524, reluController Référence d'objet. 512, jamais relié, a été supprimé. La cible est conforme à la SPEC : la marche litPC->GetControlRotation()(Source/LifePlay/LPDirectControlSubsystem.cpp:887).Pion|SetControlRotationa donné d'emblée la version Controller (516, 518, 523 : selfController). - E2 (procédure, même résultat) : les 5 coupures nommées par la fiche (453 → 454.A, 292 → PO28.A, 96.then → 67,
293 → PO30.A, 97.then → 68) n'ont pas été faites par
break_pins. Elles résultent dudelete_nodedu nœud à retirer, fait avant de relier le remplaçant : un nœud supprimé perd tous ses liens. Motif : le budget de tours (60), qui n'aurait pas couvert 5 appels de plus. Le résultat est vérifié au § 1.7 : chaque entrée n'a qu'un lien, celui du remplaçant. Seule coupure explicite : CE48.then → 461, puisque 461 reste en place. - E3 (types) :
BreakRotator.Yaw(simple précision) → PO28.A / PO30.A (double) et PO28 / PO30 (double) →MakeRotator.Yaw(simple) : conversions implicites. PO28 et PO30 restentfloat+floaten double. La compilation stricte les accepte. - E4 (contrôle partagé) : un seul
GetControlRotation(524) et un seulBreakRotator(514), purs, servent B2d-1 à B2d-3, comme le permet la SPEC (l. 392). Ils sont reliés de loin :ComputeC2Targetest en x ≈ 34000, CircleCamera et LeftRight en x ≈ 5000-6000. Effet cosmétique seulement. - E5 (fait inattendu, sans effet sur le graphe) : après sa suppression,
get_node_infossur 453 rend encore un objet (broches vides), alors que 512 est refusé (not valid EdGraphNode).find_nodesétablit que 453 n'est plus dansEventGraph. Hypothèse estimée : l'objet reste en mémoire, tenu par le tampon d'annulation, jusqu'au ramasse-miettes ou à l'enregistrement. À trancher par le dump après enregistrement (§ 5.1). - Positions (cosmétique, non prescrites) : les nœuds de B2d-4 sont en x 37000-37750, y 3000-3100. Ils sont donc loin de CE48 (34000, 3000) et de 461 (34300, 3000), qui n'étaient pas connus à la création.
- Garde
Accessed None: aucuneIsValidajoutée. « Vide » n'est pas un état normal deGI.PC(262) ni deGI.PlayerCharacter(261) en C2, et ni la SPEC ni la fiche n'en demandent. Les lectures nouvelles sont au § 5.3. - Comportement : hors C2,
CircleCameraetLeftRightsont inchangés (elserelus). En C2, le changement est voulu par la SPEC : la rotation de contrôle remplaceC2Yaw.PlaceC2pose désormais la rotation de contrôle avantEnsureCamRoom.
3. Instantané
Fait par l'orchestrateur (non refait, consigne) : D:\Game\LifePlay_Backup_5.5\CAM-B2d\. Relevé :
date -Iseconds; md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/Room.uasset"; grep -i "Room.uasset" "/d/Game/LifePlay_Backup_5.5/CAM-B2d/manifest.csv"; git -C "/d/Game/LifePlay-master 5.8" status --short -- Content/BPs/Room.uasset; git -C "/d/Game/LifePlay-master 5.8" rev-parse --short HEAD
2026-10-13T17:19:04+02:00
e0517c63541f249bd9dbda5c39815ba5 */d/Game/LifePlay-master 5.8/Content/BPs/Room.uasset
"Content\BPs\Room.uasset","True","6318319","E0517C63541F249BD9DBDA5C39815BA5","2026-10-13T17:11:26"
M Content/BPs/Room.uasset
0745917
Le MD5 du disque après B2d est celui de l'instantané : rien n'a été enregistré (attendu). Le M vient de B2b, déjà
enregistré et non commité. Retour arrière avant l'enregistrement de B2e : fermer l'éditeur sans enregistrer, ce qui perd
B2d. Après : Tools\Restore.ps1 -Lot CAM-B2d -Only Content\BPs\Room.uasset, éditeur fermé, sur décision de
l'orchestrateur (jamais lancé ici).
4. Ce qui n'a pas été fait
save_assets: non fait, sur consigne de l'orchestrateur (B2e enregistre Room une fois). B2d vit en mémoire de l'éditeur PID 22060 : une fermeture ou un plantage avant l'enregistrement de B2e le perd ; l'instantané reste intact.- Mesures de la fiche « MD5 de Room après l'enregistrement (≠ E0517C63) » : sans objet ici, à prendre après B2e.
- Absence de 67, 68, 292 et 293 dans le graphe : non prouvée par
find_nodes, faute de tours. Elle est établie seulement pardelete_node→nullet par l'absence de leurs liens sur PO28, PO30, 96 et 97 (§ 1.7). hyper_blueprint_debug_inspectet journal de l'éditeur : non consultés (§ 1.8).- Comportement à l'exécution : non mesuré (pas de PIE ni de build), seulement déduit du câblage. La preuve en jeu
(build empaqueté +
LogStats) n'est pas de mon rôle.
5. Points à confirmer en VAL
- V5 (
CAM-SPEC.md:393), après l'enregistrement de B2e, par dump :GetControlRotation≥ 1 (524, de classeController) ;SetControlRotation= 3 (516, 518, 523) ; aucunGetActorForwardVectordansComputeC2Target(453 absent du fichier, cf. E5) ;C2Yawécrite 0 fois (aucunSetC2Yaw: 67 et 68 absents) ; plus deVariableGet_292ni_293; critères de B2b inchangés. - Hors C2 :
CircleCamera→IfThenElse_26etLeftRight→IfThenElse_9sont inchangés (relu). À confirmer en jeu. LogStats: aucunAccessed Nonenouveau surVariableGet_262,_261,CallFunction_519,_523,_524. Lectures nouvelles :GI.PC(262) dansComputeC2Target(524 ; avant, cette fonction lisait 261 par 453) et dansPlaceC2avantEnsureCamRoom(523 ; 262 était déjà lu plus loin, par 466) ;GI.PlayerCharacter(261) dansPlaceC2(519). Dans CircleCamera et LeftRight, 262 est déjà lu par la condition (PO8 → 439) avant 516 / 518 : aucun risque nouveau là.- Essai B2d de la fiche (l. 49-53) : Z s'éloigne de la caméra ; souris et flèches tournent la caméra ; la caméra est
derrière le personnage en entrant en C2. Estimé, non lu : si
Personutilise la rotation de contrôle en lacet (bUseControllerRotationYaw),SetControlRotationfera aussi tourner le personnage. À observer. SetControlRotation(0, 0, Yaw)remet à 0 le tangage et le roulis de contrôle à chaqueCircleCamera,LeftRightetPlaceC2. C'est voulu (SPEC :MakeRotator(0, 0, …)) ; la hauteur de C2 reste réglée parC2Height.- Le lacet de contrôle n'est pas borné (somme libre).
SetControlRotationne normalise pas (estimé, non lu). Sans effet surGetForwardVector. - Compilation stricte jugée sur l'absence d'exception seulement : à recontrôler après B2e et par
CompileAllBlueprints(V6). - E5 : vérifier que l'enregistrement de B2e n'écrit pas 453 (dump : aucun
GetActorForwardVectordans Room, sauf d'autres nœuds éventuels horsComputeC2Target, à lister).
6. Fichiers à commiter
A Docs/Migration/etat/CAM/CAM-B2d-IMPL.md(dépôt du jeu, brancheue5.8).Content/BPs/Room.uasset: rien pour B2d seul (non enregistré). Il sera enregistré par B2e, puis commité après l'essai commun (fiche, § « Après »).
Tours : 59 / 60
Constats
- À verser :
lp-mcp§ 3.create_nodeaccepte un paramètre cachédeclaring_class(ex."/Script/Engine.Controller") qui choisit la classe quand deux fonctions ont le mêmetype_id. Sans lui,Pion|GetControlRotationdonne la version Pawn. On le voit en provoquant une erreur de conversion (posen chaîne) : la liste des paramètres est rendue. - À verser :
lp-mcp§ 3. Index de broches relus :GetControlRotation(Controller) : self in 0, out 0 ;SetControlRotation: execute in 0, self in 1, NewRotation in 2, then out 0 ;BreakRotator: InRot in 0, Roll out 0, Pitch out 1, Yaw out 2 (simple précision) ;MakeRotator: Roll in 0, Pitch in 1, Yaw in 2, out 0 ;Math|Vecteur|GetForwardVector: InRot in 0, out 0 ;Transformation|GetActorRotation: self in 0, out 0.
- À verser :
lp-mcp§ 3. Un nœud supprimé peut encore se lire par sonrefPath(broches vides). Son absence se prouve parfind_nodes {title}, qui cherche par sous-chaîne, avec un témoin positif. - Les numéros de nœud ne sont pas consécutifs :
CallFunction_521existait déjà, et leMakeRotatorsuivant est 522. Toujours relire lerefPathrendu parcreate_node.