Fiche STE-H2 — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-opus-5-5 — Agent lp-cart — Skills sk-protocol, lp-mcp (§ 3-4), lp-commandlets, lp-paths, ue-cpp-build (préchargés), lp-doc (gabarits) # STE-H2-DIAG : F4, `GetSkeletalMesh` nul dans `UpdatePlayerMeshWithMorphs` (ERR-085) (2026-10-10) Lecture seule stricte (aucun éditeur, commandlet, build ni écriture hors de ce fichier). Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8`, HEAD `5b34499`. Gabarit DIAG : `D:\Game\LifePlay-master 5.8\.claude\skills\lp-doc\gabarits.md:32-34`. **Statut : fait.** H2 confirmée ; correctif proposé : 1 garde, 2 nœuds, 1 `break_pins`, 5 `connect_pins`, comportement identique. État du Blueprint (commande `md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/GI.uasset"`) : `d93785fca90e3342575fc0b601cc2e93`, égal à `STE-D-INV.md:84,102` ; `git status --short -- Content/BPs/GI.uasset` vide ; dernier commit de GI `bc4d91f` (STC, CHG-296). Dump lu : MD5 `6bcce837…` = `STE-D-INV.md:95`. Le dump **EG** décrit donc le GI actuel (établi). Abréviations (chemins absolus) : - **EG** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\GI__EventGraph.txt` (STE-D2, 6 218 nœuds, état 5.8 après STC) ; - **GAS** / **LP** / **GS** = `...\etat\ST\STE\dumps\GI__GenerateAndApplyStrings.txt` / `GI__LoadPreset.txt` / `GI__GetSpecific.txt` (STE-D, 5.8) ; - **PEG** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STB\dumps\Person__EventGraph.txt` (Person, dump STB, antérieur à STC ; Person modifié ensuite par `fcdaf24` CHG-284 et `ee2821a` CHG-285, `git log -- Content/BPs/Person.uasset`) ; - **DD** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\PROP\PROP_LPDumpFunction_20261008-010647.txt` (`destroyDummy`, **copie 5.5** : journal `d:\Game\LifePlay-master 5.5\...`, DD:1-4) ; - **L2** = `D:\Game\LifePlay_Build_5.8_STB-JEU2_Development\Windows\LifePlay\Saved\Logs\LifePlay.log` ; - **P10** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\U58\U7\P10\5.8\LifePlay.log` (5.8 avant STB) ; - **UE** = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\` (moteur installé, lu). ## 1. Symptôme et reproduction - L2:5173-5193 : `CHECKING: …dating/sociable_dating.lpscene` → `Person_C_2147481850 index SPAWN (test0304)` (5174) → `Accessed None trying to read (real) property CallFunc_GetSkeletalMesh_ReturnValue in not an UClass`, contexte `GI_C`, `ExecuteUbergraph_GI:64X` (5175-5177), pile `UpdateCheckForScenes` → `CheckConditionsWho` → `ReadSceneLine` → `CallFunction` → `GetSpecific` → `LoadPreset` → `GenerateAndApplyStrings` → `PlayerChangeRace` → `ExecuteUbergraph_GI` → `UpdatePlayerMeshWithMorphs` → `ExecuteUbergraph_GI` (5178-5191) → `test0304 DESTROY… Slot: 1` (5193) → `DestroyDummy was from CheckScenes, WHO unmatched` (5199). - Même pile en 5.8 avant STB, via `getPerson` : P10:39196-39221 (mannequin détruit aussitôt) et P10:40222-40239. - Comptes sur L2 (Grep, rejouables) : | Motif Grep | Mode | L2 | |---|---|---| | `property CallFunc_GetSkeletalMesh_ReturnValue` | count | 99 | | multiligne `property CallFunc_GetSkeletalMesh_ReturnValue in not an UClass\r?\n\s+GI_C [^\n]*\r?\n\s+Function /Game/BPs/GI\.GI_C:ExecuteUbergraph_GI:` | count | 96 | | `Function /Game/BPs/GI\.GI_C:UpdatePlayerMeshWithMorphs` | count | 96 | | `property CallFunc_GetSkeletalMesh_ReturnValue_\d+` | content | 1 : L2:1275, `_3`, `GI_C:SavePreset` (1277) | | multiligne, contexte hors GI `property CallFunc_GetSkeletalMesh_ReturnValue[^\n]*\r?\n\t(?:[^G]\|G[^I])[^\n]*\r?\n[^\n]*` | content | 1 : L2:1379, `CharGen_C:PopulateGenitalSlidersDaz` (1381) | | reste : ligne de résumé `LogLifePlay` « 96 [LogScript Warning] … » | — | L2:30939 | Donc 99 = 96 (F4) + 1 (`SavePreset`) + 1 (`CharGen`) + 1 (résumé). Dénominateurs (repris de `STD-SCN-DIAG.md:29, 47`, non recomptés) : 123 `index SPAWN`, 185 `DestroyDummy was from CheckScenes`, 96 SPAWN suivis de F4. ## 2. Hypothèse testée (une seule) **H2** (`STD-SCN-DIAG.md:187-188`) : le maillage (corps) du mannequin recyclé est vide quand il est repris (vidé au `DESTROY`), et `LoadPreset` → `GenerateAndApplyStrings` → `PlayerChangeRace` déclenche `UpdatePlayerMeshWithMorphs`, qui lit les morphs du corps **avant** que le corps soit réaffecté. Réfutée si : le corps est affecté avant la lecture, ou le mannequin a un corps au SPAWN. ## 3. Essai et résultat brut ### 3.1 Le mannequin est le personnage édité pendant `LoadPreset` (établi) GS:1084-1088 `SpawnDummy` (`K2Node_CallFunction_32`) → `PersonUsed` ; GS:220-224 `K2Node_VariableSet_6783` `CharacterBeingEdited` ← `PersonUsed` ; GS:236-240 `then` → `LoadPreset` (`K2Node_CallFunction_34239`). LP:149-165 boucle sur les lignes du préréglage (`ReadLine`), LP:202-219 type `String` → `GenerateAndApplyStrings` (`K2Node_CallFunction_25370`). Aucun `SetSkinnedAssetAndUpdate` dans LP (Grep `SetSkinnedAssetAndUpdate` sur `dumps\*__*.txt` : 0 ligne dans LP). ### 3.2 Chaîne jusqu'à l'événement (établi) - GAS:698-717 : `GeneratedValue` = `Asian` / `African` / `Caucasian` ; GAS:727-737 `Conv_StringToText` → `NewRace` de `K2Node_CallFunction_60714` `PlayerChangeRace` (exécuté par `MacroInstance_2353.Completed`). - EG:1096-1099 `K2Node_CustomEvent_806` `PlayerChangeRace` → `K2Node_IfThenElse_1471` (EG:1275-1279), condition `EqualEqual_StriStri(NewRace, VariableGet_3400.Race)` (EG:19126-19138). `else` (race différente) → `K2Node_VariableSet_712` `Race` (EG:4101-4106) ; `then` (même race) → `CheckCbeGenderModel` (`K2Node_CallFunction_782`) → `K2Node_IfThenElse_139`, `else` → le même `VariableSet_712` (EG:25439-25448). - `VariableSet_712.then` → `K2Node_CallFunction_10834` `UpdatePlayerMeshWithMorphs` (EG:1280-1283) → `UpdateColorBasedOnRace` (EG:1415-1418) → `UpdateDefaultHair` (EG:19514-19517). - Second appelant, couvert par la même garde : `PlayerChangeGender` → `RemoveFacialHair` → `K2Node_CallFunction_8720` (EG:1092-1109, 4730-4734, 1163-1166). ### 3.3 Lecture des morphs avant l'affectation du corps (établi) `K2Node_CustomEvent_1498` `UpdatePlayerMeshWithMorphs` (EG:1160-1162) → `Array_Clear(UsedMorphs)` `CallArrayFunction_232` (EG:1173-1177) → `Array_Clear(UsedMorphValues)` `CallArrayFunction_231` (EG:1178-1182) → `ForEachLoop` `MacroInstance_207` (EG:1192-1198) : `Array` ← `VariableGet_7134` `MorphTargets` (EG:1189-1191), `self` ← `CallFunction_1101` `LPMeshCompat::GetSkeletalMesh` (EG:29255-29258), `Component` ← `CharacterBeingEdited.Mesh` (`VariableGet_7383` / `_7384`, EG:1183-1188). Corps de boucle : `GetMorphTarget` ≠ 0 → `Array_Add` des noms et valeurs (EG:1199-1240). `Completed` → `K2Node_CallFunction_10426` `UpdatePlayerMesh` (EG:1241-1244) → `ForEachLoop` `MacroInstance_219` sur `UsedMorphs` → `SetMorphTarget` sur `CharacterBeingEdited.Mesh` (EG:1254-1274). Affectation du corps, **après** la boucle : `UpdatePlayerMesh` (`K2Node_CustomEvent_1370`, EG:1147-1149) → `Map_Clear` (EG:19252-19254) → `ForEachLoopWithBreak` sur `MeshSexRace` (EG:1125-1132) → `IfThenElse_1044` (sexe et race égaux, EG:1133-1137, 1150-1159, 19154-19158) → `LoadMeshIfNeeded` (EG:19661-19663) → `K2Node_CallFunction_5556` `SetSkinnedAssetAndUpdate(self = CharacterBeingEdited.Mesh, NewMesh = MeshSexRace.Mesh)` (EG:1110-1121, 1138-1140). Les 3 `SetSkinnedAssetAndUpdate` de GAS visent `HairSM` (GAS:924-929, 984-989) et un `SMC` de vêtement (GAS:1232-1237) : aucun n'affecte le corps avant `PlayerChangeRace` (estimé pour `CallFunction_259.SMC`, non suivi). ### 3.4 Le mannequin repris n'a pas de corps (établi par le journal) L2:5160-5172 : `GetSocketInfoByName(...): No SkeletalMesh for Component(CharacterMesh0) Actor(... Person_C_2147481850)` aux images 290-291, **avant** le SPAWN de L2:5174 sur le même acteur ; de même après le `DESTROY` (L2:5194-5198) et en P10:39216-39220. ### 3.5 Où le corps est vidé (estimé fort ; dumps 5.5 et STB) - DD:139-143 : `destroyDummy` appelle `SetActorHiddenInGame(true)` puis `Person_C::ResetVars()` ; DD:179-192 : texte `test0304 DESTROY` (celui de L2:5193). - PEG:11873-11875 : `ResetVars` est un événement de Person → `VariableSet_221` (PEG:18701-18703) → … → `VariableSet_60` (PEG:12440-12442) → `VariableSet_109` (PEG:12500-12502) → `ClearSMCs` `K2Node_CallFunction_372` (PEG:12293-12295) → `ClearMorphTargets` `K2Node_CallFunction_458` (PEG:13366-13368) → `K2Node_CallFunction_433` `SetSkinnedAssetAndUpdate(self = Mesh, HairSM, PenisSM ; NewMesh vide)` (PEG:13245-13253). Même nœud `_433` dans le dump STA (`...\etat\ST\STA\dumps\Person__EventGraph.txt:13129`). - Non suivi : les maillons entre `VariableSet_221` et `VariableSet_60` ; pas de dump 5.8 actuel de `destroyDummy` ni de Person. ### 3.6 Nom compilé ↔ nœud (établi par élimination) Le message nomme la variable qui tient le nul (UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3213-3216`). La pile s'arrête dans `ExecuteUbergraph_GI` sous `UpdatePlayerMeshWithMorphs`, sans cadre `UpdatePlayerMesh` (L2:5189-5191). Dans cet événement, le seul `GetSkeletalMesh` évalué avant le corps de boucle est `_1101` (EG:1160-1198, 1241-1274 ; `GetMorphTarget` `_10253` et `SetMorphTarget` `_10465` prennent le composant `Mesh`, pas le maillage). `CharacterBeingEdited` et `Mesh` ne sont pas nuls (sinon le message nommerait `CharacterBeingEdited` ou `Mesh`) : c'est l'asset du composant qui manque, `Cast(GetSkinnedAsset())` = nullptr (`D:\Game\LifePlay-master 5.8\Source\LifePlay\LPMeshCompat.cpp:9-19`). ### 3.7 Ce que fait le jeu aujourd'hui avec le nul (établi pour le graphe, estimé fort pour la macro) Lecture `MorphTargets` sur nul : avertissement, puis valeur effacée (UE `ScriptCore.cpp:3234-3245`) → tableau vide → `ForEachLoop` : 0 tour → `Completed` → `UpdatePlayerMesh` (corps affecté) → boucle `_219` sur `UsedMorphs` vidé à EG:1173 : rien. Un avertissement par appel, cohérent avec 96 F4 pour 96 SPAWN suivis de F4. ## 4. Verdict : **confirmée** Les deux volets sont vérifiés : le mannequin repris n'a pas de corps au SPAWN (§ 3.4, journal) ; `PlayerChangeRace` → `UpdatePlayerMeshWithMorphs` lit `MorphTargets` du corps (§ 3.3, `_1101` → `_7134` → `_207`) avant `UpdatePlayerMesh`, seul endroit du chemin qui affecte le corps (`_5556`). Le vidage au `DESTROY` (`ResetVars` → `ClearSMCs` → `ClearMorphTargets` → `SetSkinnedAssetAndUpdate(None)`) est estimé fort (§ 3.5). L'état « sans corps » d'un mannequin en réserve est donc **voulu** par le jeu : la garde `IsValid` respecte la règle 4 de lp-mcp (vide = état normal, à écrire au CHG). Origine 5.5 / 5.8 : **laissée ouverte** (fiche § 5 ; `STD-SCN-DIAG.md:134-136`). ## 5. Hypothèse suivante proposée, et à quel niveau - **H2-O (N2, lp-cart puis utilisateur)** : origine 5.5 ou 5.8. Contre-épreuve 5.5 sur une partie avec relations (`DestroyDummy was from CheckScenes` > 0), même Grep multiligne qu'au § 1 (format 5.5 : retirer `(real) `). - **H4 (F5, N2)** : même racine probable (corps vidé au `DESTROY`, PEG:13245-13253, mannequin interrogé par image). Lecteur de sockets à identifier avant toute décision sur la cause (Q1). ## Correctif proposé (exécutable par MCP, conventions de `STE-H3-SPEC.md:55-70`) Un seul fichier : `Content\BPs\GI.uasset`, graphe `EventGraph`. Garde **G-H2** entre `_231` et `_207`. Total : **2 nœuds créés, 1 `break_pins`, 5 `connect_pins`, 0 suppression** ; EventGraph 6 218 → 6 220 nœuds. Préalables (dans l'ordre) : - P1. MD5 `GI.uasset` = `d93785fc…` ; sinon refaire le dump `LPDumpFunction -BP=GI -Fn=EventGraph -Pins -Exec` (lp-commandlets) et recontrôler les 4 nœuds cités (STE-H3 passe peut-être avant : ne touche pas l'EventGraph, `STE-H3-SPEC.md:235`). - P2. Gel annoncé (lp-mcp § 1.7), `.\Tools\Snapshot.ps1 -Lot STE-H2` (GI.uasset), `AssetTools.load_asset` `/Game/BPs/GI`. - P3. `get_node_infos {nodes:[{refPath:"/Game/BPs/GI.GI:EventGraph.K2Node_"}]}` pour `CallArrayFunction_231`, `MacroInstance_207`, `CallFunction_1101`, `CallFunction_10426` : liens = colonne « avant » ; tout écart arrête l'IMPL. Relever aussi les indices de broches et la position de `_231`. | Nœud existant | Broche | Avant (EG) | Après | |---|---|---|---| | `K2Node_CallArrayFunction_231` (`Array_Clear` `UsedMorphValues`) | `then` (sortie) | → `MacroInstance_207.Exec` (1180) | → `G-H2-B.execute` | | `K2Node_MacroInstance_207` (`ForEachLoop`) | `Exec` (entrée) | ← `_231.then` (1193) | ← `G-H2-B.then` | | `K2Node_CallFunction_1101` (`LPMeshCompat::GetSkeletalMesh`, pur) | `ReturnValue` (sortie) | → `VariableGet_7134.self` (29258) | garde ce lien, + `G-H2-V.Object` | | `K2Node_CallFunction_10426` (`UpdatePlayerMesh`) | `execute` (entrée) | ← `_207.Completed` (1242) | garde ce lien, + ← `G-H2-B.else` | Étapes : 1. `create_node {graph:{refPath:"/Game/BPs/GI.GI:EventGraph"}, type_id:"Utilitaires|IsValid", pos:{x:+250, y:+150}}` → **G-H2-V**. 2. `create_node {…, type_id:"Utilitaires|ContrôleDeFlux|Branche", pos:{x:+250, y:}}` → **G-H2-B**. 3. `break_pins` : sortie `_231.then` (index relu, attendu 0) / entrée `_207.Exec` (index relu, attendu 0). 4. `connect_pins` (5) : - `_231.then` → `G-H2-B.execute` (entrée 0) ; - `_1101.ReturnValue` (sortie relue, attendue 0) → `G-H2-V.Object` (entrée relue, attendue 1, `self` caché) ; - `G-H2-V.ReturnValue` (sortie relue, attendue 0) → `G-H2-B.Condition` (entrée 1) ; - `G-H2-B.then` (sortie 0) → `_207.Exec` ; - `G-H2-B.else` (sortie 1) → `_10426.execute` (entrée relue, attendue 0). 5. `get_node_infos` des 6 nœuds ; `compile_blueprint` ; `hyper_blueprint_debug_inspect {blueprintAssetPath:"/Game/BPs/GI.GI", graphName:"EventGraph", pageSize, cursor:""}` = 0 diagnostic ; `save_assets {asset_paths:["/Game/BPs/GI"]}`. Indices : Branch établis par lp-mcp § 3 ; les autres sont des estimations à relire (règle de `STE-H3-SPEC.md:60-64`). Comportement : - Corps présent : `IsValid` vrai → `_207` comme avant. Seul ajout : une évaluation de plus de `_1101`, sans effet de bord (`LPMeshCompat.cpp:9-19`). **Identique** (établi). - Corps absent : aujourd'hui avertissement, boucle à 0 tour, `UpdatePlayerMesh` (§ 3.7) ; après : `else` → `UpdatePlayerMesh`, sans lecture. Les deux `Array_Clear` restent avant la garde. **Identique** (établi pour les nœuds à effet ; estimé fort pour la macro `ForEachLoop` à 0 tour). - Effet visible (DIAG STD § 4, mannequin gardé pour la scène) : **inchangé par la garde**, qui exécute les mêmes nœuds à effet. Indice sur l'état actuel (estimé, non vérifié en jeu) : `SetMorphTarget` range la valeur dans `MorphTargetCurves` sans exiger d'asset (UE `Runtime\Engine\Private\Components\SkeletalMeshComponent.cpp:4011-4035`) ; seul `ClearMorphTargets` la vide (`:4037-4040` ; Grep `MorphTargetCurves\.(Empty|Reset)` sur ce fichier → 1 ligne) ; `SetSkinnedAssetAndUpdate` → `SetSkeletalMesh` (`:3595-3598`). Les morphs posés avant le corps survivraient donc à son affectation (à condition que `GenerateAndApplyFloats`, non dumpé, passe par `SetMorphTarget`). Pas de question à l'utilisateur pour la garde. Repli : `.\Tools\Restore.ps1 -Lot STE-H2` éditeur fermé (GI entier), puis MD5 = `d93785fc…` ; après commit : `git revert` du commit du ticket STE-H2 (gardien). Interdits : ni Person, ni `destroyDummy`, ni `ResetVars` (cause, Q1) ; pas de macro `IsValid` à exécution (convention H3) ; pas d'écriture HyperAIStudio ; pas de commit par l'agent. ## Critères de VAL (commande → attendu) 1. `.\Tools\RunCommandlet.ps1 -Lot STE-H2 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=EventGraph -Pins -Exec'` → 6 220 nœuds ; liens du tableau « après » relus ; `python Tools\DiffRemplacementNoeuds.py ` sans autre écart. 2. `hyper_blueprint_debug_inspect` EventGraph = 0 diagnostic ; `CompileAllBlueprints` éditeur fermé : 0 erreur (lp-validate). 3. Journal de jeu (build Development, parcours de STB-JEU2 avec relations, ≥ 3 min en ville, `STD-SCN-DIAG.md:198-206`), Grep : - multiligne GI du § 1 (count) → **0** (L2 : 96) ; - `Function /Game/BPs/GI\.GI_C:UpdatePlayerMeshWithMorphs` (count) → **0** (L2 : 96) ; - `index SPAWN \(test0304\)` > 0 et `DestroyDummy was from CheckScenes` > 0, sinon `BLOCKED` (non mesuré) (L2 : 123 / 185) ; - `property CallFunc_GetSkeletalMesh_ReturnValue` (count) : **pas 0** attendu, reste `CharGen` / `SavePreset _3` si le parcours passe par l'éditeur de personnage (L2:1275, 1379) : information seulement (écart à la fiche, § Constats) ; - `No SkeletalMesh` / `CHECKING:` : information (F5, hors H2 ; L2 : 15 793 / 2 318). 4. Utilisateur (case) : un mannequin gardé pour une scène (ex. `meet_couple`) a son corps et ses morphs comme avant. ## Questions **Q1 (utilisateur)** : garde seule maintenant (résultat identique, ci-dessus), ou instruire aussi la **cause** (le corps est vidé au `DESTROY` par `ResetVars` → `SetSkinnedAssetAndUpdate(None)`, PEG:13245-13253 : le garder, ou donner le corps avant `LoadPreset`) ? La cause changerait un comportement (mannequin caché avec corps, mémoire, F5) : à joindre à H4. Recommandation : garde seule. Incertitudes : maillons `VariableSet_221` → `VariableSet_60` de `ResetVars` non suivis ; dumps de `destroyDummy` (5.5) et de Person (STB) antérieurs à l'état actuel ; indices de broches (relus en P3) ; branche de `PlayerChangeRace` réellement prise (race différente ou `CheckCbeGenderModel` faux, non dumpé) : sans effet sur la garde ; origine 5.5 / 5.8 ouverte. Fichiers à commiter (dépôt jeu, branche `ue5.8`, ticket au gardien) : - A `Docs/Migration/etat/ST/STE/STE-H2-DIAG.md` Tours : 29 / 60 ## Constats - Écart à la fiche § 7 : « Grep `CallFunc_GetSkeletalMesh_ReturnValue` attendu à 0 » ne peut pas valoir 0 : 2 autres nœuds hors F4 portent ce nom (`CharGen_C:PopulateGenitalSlidersDaz`, L2:1379-1381 ; `GI_C:SavePreset`, `_3`, L2:1275-1277), plus la ligne de résumé L2:30939. Critère remplacé par le Grep multiligne GI et le Grep de pile (VAL 3). ERR-085 (« 99, 96 en variante GI », `D:\Game\LifePlay-master 5.8\Docs\Registre_Erreurs.md:92`) se lit donc 96 + 1 + 1 + 1. - Dumps hors STE utilisés et signalés : DD (copie 5.5, `destroyDummy`) et PEG (Person, STB) ; aucun dump STB de GI utilisé. - Gabarit lu aux deux chemins (`D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md:32` et la jonction 5.8 `:32`) : section DIAG identique.