20 KiB
Fiche STE-H10-GI2 — Niveau N3 — Modèle prévu opus (xhigh) | utilisé claude-opus-5-5 — Agent lp-mcp-bp — Skills sk-protocol, sk-change-safety, lp-mcp, lp-lot
STE-H10-GI2-IMPL : B1-a (SavePreset) puis B2-a (GetSMBoolForSMC_Cloth) dans GI, par MCP (2026-10-13)
Résultat : réussi, côté éditeur. Les deux blocs sont écrits, relus conformes, compilés et enregistrés.
| Bloc | Nœuds | Coupures | Liens | Valeur posée | MD5 de GI après save_assets |
|---|---|---|---|---|---|
B1-a, SavePreset |
503 → 506 | 1 | 4 | S3 = true | 9a8a61e8… |
B2-a, GetSMBoolForSMC_Cloth |
11 → 13 | 2 | 4 | variable locale MeshSuffix |
71034a04… |
Les trois compilations de GI (avertissements en erreurs) sont sans ligne Error ni Warning dans le journal de l'éditeur. Person
et TopRight sont inchangés. La preuve en jeu (build empaqueté + LogStats) n'est pas de mon ressort.
BUILD-GO : « [BOSS→STE] BUILD-GO STE-H10 (a), GI seul » (Big Boss, 2026-10-13), le même que pour STE-H10-GI1. Éditeur headless PID 8296, port 8000, connexion partagée avec la session STE [b84cc5]. Accord de l'utilisateur : « tu peux y aller mcp ouvert ».
Trois passages :
| Passage | Agent | Tours | Travail | Arrêt |
|---|---|---|---|---|
| 1 | a6d948272a33b8bfb |
24 | écrit le bloc SavePreset et le compile |
refus du crochet (§ 2) |
| 2 (reprise) | ad9203bcfd2e63d2b |
15 / 60 | recompte, relit, recompile | refus du crochet (§ 2) |
| 3 (reprise) | ad9203bcfd2e63d2b |
35 / 45 | enregistre B1-a, écrit et enregistre B2-a | — |
L'orchestrateur a fait certains contrôles entre les passages 2 et 3. Ils sont cités comme tels.
1. Fichiers modifiés
Fichier modifié : Content\BPs\GI.uasset, deux fois, par save_assets seulement.
Relevés d'empreinte. Commande (Git Bash, copie 5.8) :
md5sum Content/BPs/GI.uasset Content/BPs/Person.uasset Content/UI/TopRight.uasset
| Moment (+0200) | GI | Person | TopRight |
|---|---|---|---|
| 00:44:39, entrée (passage 1) | 324a4f271203d4ed8758b6db73ad0a35 |
d1b4c63d47621b1191ad9352c80c99cd |
1fcb94a60061b418e9deeb08bc1a990d |
| avant le passage 3 (relevé de l'orchestrateur) | 324a4f271203d4ed8758b6db73ad0a35 |
— | — |
00:51:51, après save_assets n° 1 (B1-a, SavePreset) |
9a8a61e8caf7dd09b8f70b1d33847171 |
d1b4c63d47621b1191ad9352c80c99cd |
1fcb94a60061b418e9deeb08bc1a990d |
00:54:31, après save_assets n° 2 (B2-a) |
71034a04c6d64e1a806ca0dadc934fb9 |
d1b4c63d47621b1191ad9352c80c99cd |
1fcb94a60061b418e9deeb08bc1a990d |
save_assets {asset_paths:["/Game/BPs/GI"]} (toolset editor_toolset.toolsets.asset.AssetTools) a rendu {"returnValue":true}
les deux fois. Journal de l'éditeur D:\Game\LifePlay-master 5.8\Saved\Logs\LANC1-headless-20261013-001639.log, heures en UTC
(locale − 2 h) :
- n° 1 : ligne 2508,
22.51.43:006,Cmd: OBJ SAVEPACKAGE PACKAGE="/Game/BPs/GI" FILE=".../Content/BPs/GI.uasset" SILENT=true; ligne 2510, déplacement versContent/BPs/GI.uasset; - n° 2 : ligne 2591,
22.54.23:201, même commande ; ligne 2593, déplacement versContent/BPs/GI.uasset.
Seul le paquet /Game/BPs/GI est enregistré dans Content (grep SAVEPACKAGE du journal). Les sauvegardes automatiques
Saved\Autosaves\Game\BPs\GI_Auto1.uasset (ligne 2316) et GI_Auto2.uasset (ligne 2498, 22.49.38) sont hors Content ; je ne
les ai pas touchées. git status --short -- Content est inchangé : M Content/BPs/GI.uasset, M Content/BPs/Person.uasset,
M Content/UI/TopRight.uasset. Person et TopRight étaient déjà modifiés avant cette fiche, et leurs MD5 n'ont pas changé.
1.1 B1-a — /Game/BPs/GI.GI:SavePreset
Comptes : 503 avant, 506 après (établi). find_nodes {graph:{refPath:"/Game/BPs/GI.GI:SavePreset"}, title:""} rend sa liste en
ligne. Je l'ai comptée dans la transcription de l'agent qui l'a reçue (dossier
%USERPROFILE%\.claude\projects\D--Game-LifePlay-master-5-8\36e41eab-6dbf-4c01-adea-9fe53389b8ce\subagents\), par des tubes, sans
écrire de fichier :
pat='GI\.GI:SavePreset\.K2Node_[A-Za-z]*_[0-9]*'
# passage 2, agent-ad9203bcfd2e63d2b.jsonl, ligne du find_nodes :
ligne 56 : total 506, uniques 506
# passage 1, agent-a6d948272a33b8bfb.jsonl :
prec ligne 243 : 503 (avant écriture)
prec ligne 306 : 506 (après compilation, passage 1)
# ( sed -n 243p <prec> | grep -o "$pat" | sort -u ; sed -n 56p <reprise> | grep -o "$pat" | sort -u ) | sort | uniq -u
GI.GI:SavePreset.K2Node_IfThenElse_11
GI.GI:SavePreset.K2Node_VariableGet_2
GI.GI:SavePreset.K2Node_VariableSet_14
# nœuds de la liste avant absents de la liste après :
0
Correspondance SPEC → nom réel → type_id. Préfixe : /Game/BPs/GI.GI:SavePreset.K2Node_. Au passage 1, chaque type_id a été
vérifié par find_node_types avant create_node. Au passage 2, type, broches et positions ont été relus par get_node_infos.
| SPEC | Nœud réel | type_id de création | Type relu | Broches (index) | Position |
|---|---|---|---|---|---|
| S1 Branche | IfThenElse_11 |
`Utilitaires | ContrôleDeFlux | Branche` | idem |
| S2 Get SaveBeforeDestroy | VariableGet_2 |
`Variables | Pardéfaut | ObtenirSaveBeforeDestroy` | ` |
| S3 Set RevertSkipped (true) | VariableSet_14 |
`Variables | Pardéfaut | DéfinirRevertSkipped` | ` |
Nœuds existants, relus avant écriture (passage 1) puis après (passage 2) :
IfThenElse_2957(Branche, 15744, 9248) :then(0) : avant →CallFunction_16039.execute; après →IfThenElse_11.executeseul ;else(1) non relié ;- entrées inchangées :
execute←IfThenElse_8249(1),CallFunction_0,CallFunction_967;Condition←CallFunction_69701.
CallFunction_16039(|RevertStripAll, 15952, 9232) :execute: avant ←IfThenElse_2957; après ←IfThenElse_11.elseseul ;self(1) ←VariableGet_9248, inchangé ;then(0) non relié.
Coupure : IfThenElse_2957.then (0) / CallFunction_16039.execute (0), break_pins → null. Relue absente.
Liens (connect_pins → null), relus deux fois par get_node_infos des 5 nœuds :
| # | Sortie | Entrée | Relu |
|---|---|---|---|
| 1 | IfThenElse_2957.then (0) |
IfThenElse_11.execute (0) |
oui, seul lien de part et d'autre |
| 2 | VariableGet_2.SaveBeforeDestroy (0) |
IfThenElse_11.Condition (1) |
oui |
| 3 | IfThenElse_11.else (1) |
CallFunction_16039.execute (0) |
oui, seule entrée de CallFunction_16039.execute |
| 4 | IfThenElse_11.then (0) |
VariableSet_14.execute (0) |
oui ; VariableSet_14.then non relié (fin de fonction) |
set_pin_value VariableSet_14.RevertSkipped (1) = true → null, relu "value":"true".
1.2 B2-a — /Game/BPs/GI.GI:GetSMBoolForSMC_Cloth
Schéma de add_variable, lu par describe_toolset editor_toolset.toolsets.blueprint.BlueprintTools. La réponse fait 74 787
caractères ; le harnais l'a enregistrée dans ...\tool-results\mcp-unreal-mcp-describe_toolset-1791845542804.txt, et je l'ai lue
par grep -o. Extrait : add_variable prend blueprint, name et type_name (requis), plus graph (« If provided, adds a
local variable to this function graph instead of a member variable ») et container_type.
Variable locale :
- avant :
list_variables {blueprint:{refPath:"/Game/BPs/GI.GI"}, graph:{refPath:"/Game/BPs/GI.GI:GetSMBoolForSMC_Cloth"}}→[]; - création :
add_variable {blueprint:{refPath:"/Game/BPs/GI.GI"}, name:"MeshSuffix", type_name:"string", graph:{refPath:"/Game/BPs/GI.GI:GetSMBoolForSMC_Cloth"}}→null; - après : même
list_variables→["MeshSuffix"]; find_node_types {graph:<GetSMBoolForSMC_Cloth>, type_id_filter:"MeshSuffix", context_pins:[]}→["Variables|Pardéfaut|ObtenirMeshSuffix","Variables|Pardéfaut|DéfinirMeshSuffix"].
Comptes : 11 avant, 13 après (établi). Même méthode de comptage, dans agent-ad9203bcfd2e63d2b.jsonl :
pat='GetSMBoolForSMC_Cloth\.K2Node_[A-Za-z]*_[0-9]*'
ligne 102 (avant, find_nodes) : 11 ; ligne 187 (apres, find_nodes) : 13
presents dans une seule liste :
GetSMBoolForSMC_Cloth.K2Node_VariableGet_0
GetSMBoolForSMC_Cloth.K2Node_VariableSet_0
absents de la liste apres :
0
Les 11 nœuds avant : FunctionEntry_2157, FunctionResult_0, VariableGet_4937, MacroInstance_1872, BreakStruct_1008,
IfThenElse_2762, CallFunction_0, CommutativeAssociativeBinaryOperator_0, CallFunction_1, CallFunction_2, CallFunction_3.
Correspondance SPEC → nom réel → type_id. Préfixe : /Game/BPs/GI.GI:GetSMBoolForSMC_Cloth.K2Node_.
| SPEC | Nœud réel | type_id de création (vérifié par find_node_types) |
Type relu | Broches (index) | Position |
|---|---|---|---|---|---|
| C1 Set MeshSuffix | VariableSet_0 |
`Variables | Pardéfaut | DéfinirMeshSuffix` | ` |
| C2 Get MeshSuffix | VariableGet_0 |
`Variables | Pardéfaut | ObtenirMeshSuffix` | ` |
Nœuds existants relus avant écriture (get_node_infos), conformes à la SPEC § 2.3 :
FunctionEntry_2157(|GetSMBoolForSMC_Cloth, -496, 0) :then(0) →MacroInstance_1872.Exec;SMC(1) →CallFunction_3.Component.MacroInstance_1872(Utilities|Array|ForEachLoopwithBreak, -80, 0) :Exec(0) ←FunctionEntry_2157.then;Array(1) ←VariableGet_4937;LoopBody(0) →IfThenElse_2762.CommutativeAssociativeBinaryOperator_0(Utilitaires|Chaîne|Append, 560, 336) :ReturnValue(0) →CallFunction_2.InSuffixseul ;A(0) etC(2) ←CallFunction_0;B(1) =".".CallFunction_2(Utilitaires|Chaîne|EndsWith, 496, 496) :SourceString(0) ←CallFunction_1;InSuffix(1) ←CommutativeAssociativeBinaryOperator_0;SearchCase(2) =IgnoreCase;ReturnValue→IfThenElse_2762.Condition.- Chaîne en amont, lue sans être touchée :
CallFunction_3(GetSkeletalMesh(UE4),Component←SMC) →CallFunction_0(GetObjectName) →AetCde l'Append ;CallFunction_1(ToString(Name), ←BreakStruct_1008(6)).
Coupures (break_pins → null, un appel chacune), relues absentes :
FunctionEntry_2157.then(0) /MacroInstance_1872.Exec(0) ;CommutativeAssociativeBinaryOperator_0.ReturnValue(0) /CallFunction_2.InSuffix(1).
Liens (connect_pins → null, un appel chacun), relus par get_node_infos sur les 6 nœuds touchés après le dernier lien :
| # | Sortie | Entrée | Relu |
|---|---|---|---|
| 1 | FunctionEntry_2157.then (0) |
VariableSet_0.execute (0) |
oui, seul lien de FunctionEntry_2157.then |
| 2 | VariableSet_0.then (0) |
MacroInstance_1872.Exec (0) |
oui, seule entrée de MacroInstance_1872.Exec |
| 3 | CommutativeAssociativeBinaryOperator_0.ReturnValue (0) |
VariableSet_0.MeshSuffix (1) |
oui, seul lien de ReturnValue |
| 4 | VariableGet_0.MeshSuffix (0) |
CallFunction_2.InSuffix (1) |
oui, seule entrée de InSuffix |
Inchangés à la relecture : MacroInstance_1872.Array, LoopBody et Array Element ; CallFunction_2.SourceString,
SearchCase (IgnoreCase) et ReturnValue ; FunctionEntry_2157.SMC ; l'Append A, B (".") et C.
1.3 Compilations
Les trois appels : compile_blueprint {blueprint:{refPath:"/Game/BPs/GI.GI"}, warnings_as_errors:true}.
| # | Passage | Réponse | Journal (UTC) | Error / Warning après |
|---|---|---|---|---|
| 1 | 1, SavePreset |
null |
ligne 2482, 22.46.06 | 0 (lignes 2483-2489 lues par Read) |
| 2 | 2, SavePreset |
null |
ligne 2492, 22.49.31 | 0 (lignes 2480-2501, relevé de l'orchestrateur) |
| 3 | 3, B2-a | null |
ligne 2583, 22.54.08 | 0 (tail -n +2583 <journal> | grep -i "error|warning" : sortie vide, journal à 2617 lignes) ; tail -n +2536 | grep -i … : vide aussi |
Les lignes LogModelContextProtocol: Error: Call to unknown method "server/discover" du journal (2380, 2535) viennent de
connexions de clients MCP. Elles ne concernent pas la compilation.
2. Écarts à la SPEC
- Graphes : aucun écart. Le bloc
SavePresetest conforme au § C.5 (second bloc) ; B2-a est conforme au § 2.3 (une variable localeMeshSuffixstring, 2 nœuds, 2 coupures, 4 liens, 11 → 13). - Procédure, passage 1, écart à la fiche (rubrique 5, « un appel MCP à la fois »).
- Faits :
break_pins(coupure) et le premierconnect_pins(IfThenElse_2957.then→IfThenElse_11.execute) sont partis dans le même message. Les deux ont répondunull. - Effet : aucun. Dans les deux ordres possibles, l'état final est le même :
thenest relié àIfThenElse_11seul, ce que confirment deux relectures. L'ordre d'exécution réel n'a pas été relevé (incertain). - Tous les autres appels MCP sont partis un par un. Pour le passage 3, le journal (lignes 2541-2582) montre un seul
Dispatchingpar appel, espacés de 3 à 5 s.
- Faits :
- Arrêt du passage 1 : refus du crochet
ecriture-gardiensur une redirection> $S/sp_apres.txtvers le dossier temporaire. Message exact :PreToolUse:Bash hook error: [python -I "$CLAUDE_PROJECT_DIR/Tools/claude/hooks/ecriture-gardien.py"]: Refus du crochet ecriture-gardien. Agent : lp-mcp-bp Ecriture: redirection vers $S/sp_apres.txt - Arrêt du passage 2 : refus du crochet sur une commande de lecture du journal (
awk 'NR>=2486' <journal>). Le>de l'expressionawk, entre apostrophes, a été lu comme une redirection. Message exact :
La faute était la mienne : la consigne prévenait du crochet. Arrêt immédiat, sans nouvel essai ; reprise sur nouvelle consigne de l'orchestrateur.PreToolUse:Bash hook error: [python -I "$CLAUDE_PROJECT_DIR/Tools/claude/hooks/ecriture-gardien.py"]: Refus du crochet ecriture-gardien. Agent : lp-mcp-bp Ecriture: redirection vers =2486'; redirection vers 2489' - Lectures hors de la liste de la fiche, sans écriture :
list_variables(avant et aprèsadd_variable), pour établir que la variable est locale et qu'elle n'existait pas. describe_toolsetcomplet (74 787 caractères) : la fiche le prescrit (rubrique 5), alors quelp-mcp§ 1 règle 6 le déconseille. La réponse est partie dans un fichier du harnais, sans charger le contexte.- Comportement, point d'attention (estimé, sans écart à la SPEC) :
- Avant B2-a, la chaîne pure
GetSkeletalMesh(SMC)→GetObjectName→Appendn'était évaluée que dans le corps de boucle, donc jamais siClothTableest vide. - Depuis B2-a, elle est évaluée une fois, avant la boucle, à chaque appel.
- Seul cas où l'écart se voit :
ClothTablevide etSMCnul. Un appel sur cible nulle (GetSkeletalMesh) pourrait alors écrire unAccessed Nonequi n'existait pas. - Dans le cas courant (
ClothTablenon vide), une seule évaluation remplace ≈ 615 évaluations. - Garde
IsValidnon ajoutée : ni la SPEC ni la fiche ne la demandent (lp-mcp§ 1 règle 4). IsGenitalExposedpasse par un cast en SkeletalMeshComponent avant l'appel (SPEC § 2.3, IG:9-68), doncSMCn'est pas nul dans ce chemin (estimé). À confirmer en VAL (§ 5).
- Avant B2-a, la chaîne pure
3. Instantané
Non relancé, sur consigne de la fiche. Instantané de repli inchangé :
D:\Game\LifePlay_Backup_5.5\STE-H10\Content\BPs\GI.uasset (FE4406A0294A8001F612CD03EE2C20CC, manifeste STE-H10, relevé à
l'ouverture de STE-H10-GI1). C'est l'état d'avant tout STE-H10 (GI1 + GI2).
Retour arrière proposé, jamais lancé, éditeur fermé et sur décision de l'orchestrateur :
.\Tools\Restore.ps1 -Lot STE-H10 -Only Content\BPs\GI.uasset -WhatIf, puis sans -WhatIf. GI revient alors à FE4406A0, ce qui
retire aussi GI1. L'état intermédiaire 324a4f27… (GI1 seul) n'est conservé dans aucun instantané.
4. Ce qui n'a pas été fait
CompileAllBlueprintsouLPDumpFunction, éditeur fermé : relève de la suite (lp-mcp§ 1 règle 3), pas de cette fiche.- Build empaqueté et
LogStats: hors rôle (lp-mcp§ 1 règle 5). - Fermeture de l'éditeur,
BUILD-FIN: à l'orchestrateur.
5. Points à confirmer en VAL
Graphes, éditeur fermé :
-BP=GI -Fn=SavePreset -Pins -Exec→ 506 nœuds ;IfThenElse_2957.then→IfThenElse_11seul ;IfThenElse_11.else→CallFunction_16039;IfThenElse_11.then→VariableSet_14(RevertSkipped= true) ;VariableSet_14.thennon relié ;IfThenElse_2957.elsenon relié.-BP=GI -Fn=GetSMBoolForSMC_Cloth -Pins -Exec→ 13 nœuds ;FunctionEntry_2157.then→VariableSet_0→MacroInstance_1872.Exec;VariableSet_0.MeshSuffix←CommutativeAssociativeBinaryOperator_0;CallFunction_2.InSuffix←VariableGet_0;MeshSuffixest une variable locale de la fonction, pas une variable membre de GI.
Fichiers et compilation :
Content\BPs\GI.uasset=71034a04c6d64e1a806ca0dadc934fb9(2026-10-13 00:54:31) ; Persond1b4c63d…et TopRight1fcb94a6…inchangés.CompileAllBlueprintséditeur fermé : 0 erreur et 0 avertissement nouveaux sur GI.
En jeu :
- aucun
Accessed Nonenouveau dansGetSMBoolForSMC_Cloth(casSMCnul avecClothTablevide, § 2) ; - mêmes états « exposé » sur une scène rejouée (SPEC, critères B2-a) ;
SavePresetn'appelle plusRevertStripAllquandSaveBeforeDestroyest vrai, et metRevertSkippedà true (B1-a).
6. Fichiers à commiter
Dépôt du jeu, copie 5.8, branche ue5.8 :
A Docs/Migration/etat/ST/STE/STE-H10-GI2-IMPL.md(ce livrable).M Content/BPs/GI.uasset(MD571034a04c6d64e1a806ca0dadc934fb9, 2026-10-13 00:54:31 ; contient GI1 + GI2), après la VAL.- Ni
Content/BPs/Person.uassetniContent/UI/TopRight.uasset: déjà modifiés avant cette fiche, inchangés par elle. - Ni
Saved/Autosaves/…: hors suivi.
Tours : 35 / 45 (passage 3 ; passage 2 : 15 / 60 ; passage 1 : 24)
Constats
add_variablecrée une variable locale quand on lui passegraph:{refPath:"/Game/BPs/X.X:<Fonction>"}. On la vérifie parlist_variables {blueprint, graph}. Ses accesseurs sortent defind_node_typesfiltré sur ce graphe (Variables|Pardéfaut|Obtenir<Var>/Définir<Var>) et le nœud créé porte|Get<Var>/|Set<Var>. Le Set a pour brochesexecute0, valeur 1,then0,Output_Get1.- Les toolsets :
save_assetsest danseditor_toolset.toolsets.asset.AssetToolset rendtrue; les outils de graphe sont danseditor_toolset.toolsets.blueprint.BlueprintTools. describe_toolsetde BlueprintTools fait 74 787 caractères sur une ligne, que le harnais enregistre soustool-results\. On en extrait un schéma pargrep -o '.\{0,200\}add_variable.\{0,2200\}' <fichier>.- Compter une réponse
find_nodessans fichier : elle est dans la transcription de l'agent (subagents\agent-<id>.jsonl). Choisir la ligne par le nombre de correspondances, car la commande Bash elle-même y figure aussi. Comparer deux listes par( l1 ; l2 ) | sort | uniq -u. - Le crochet
ecriture-gardienlit tout>comme une redirection, même entre apostrophes (awk 'NR>=N'), ainsi qu'une cible$VAR/…. Pour lire une fin de journal :tail -n +N <fichier> | grep …, ou Read avecoffset. - Le journal de l'éditeur est horodaté en UTC (22.54 UTC = 00:54 locale, +0200).
- Les numéros de nœuds sont uniques par graphe, pas par Blueprint (
VariableSet_0,VariableGet_0dans une fonction de GI).