18 KiB
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 parfcdaf24CHG-284 etee2821aCHG-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 : journald:\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, contexteGI_C,ExecuteUbergraph_GI:64X(5175-5177), pileUpdateCheckForScenes→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-737Conv_StringToText→NewRacedeK2Node_CallFunction_60714PlayerChangeRace(exécuté parMacroInstance_2353.Completed). - EG:1096-1099
K2Node_CustomEvent_806PlayerChangeRace→K2Node_IfThenElse_1471(EG:1275-1279), conditionEqualEqual_StriStri(NewRace, VariableGet_3400.Race)(EG:19126-19138).else(race différente) →K2Node_VariableSet_712Race(EG:4101-4106) ;then(même race) →CheckCbeGenderModel(K2Node_CallFunction_782) →K2Node_IfThenElse_139,else→ le mêmeVariableSet_712(EG:25439-25448). VariableSet_712.then→K2Node_CallFunction_10834UpdatePlayerMeshWithMorphs(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 :
destroyDummyappelleSetActorHiddenInGame(true)puisPerson_C::ResetVars(); DD:179-192 : textetest0304 DESTROY(celui de L2:5193). - PEG:11873-11875 :
ResetVarsest un événement de Person →VariableSet_221(PEG:18701-18703) → … →VariableSet_60(PEG:12440-12442) →VariableSet_109(PEG:12500-12502) →ClearSMCsK2Node_CallFunction_372(PEG:12293-12295) →ClearMorphTargetsK2Node_CallFunction_458(PEG:13366-13368) →K2Node_CallFunction_433SetSkinnedAssetAndUpdate(self = Mesh, HairSM, PenisSM ; NewMesh vide)(PEG:13245-13253). Même nœud_433dans le dump STA (...\etat\ST\STA\dumps\Person__EventGraph.txt:13129). - Non suivi : les maillons entre
VariableSet_221etVariableSet_60; pas de dump 5.8 actuel dedestroyDummyni 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<USkeletalMesh>(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 dumpLPDumpFunction -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_<Nom>"}]}pourCallArrayFunction_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 :
create_node {graph:{refPath:"/Game/BPs/GI.GI:EventGraph"}, type_id:"Utilitaires|IsValid", pos:{x:<x231>+250, y:<y231>+150}}→ G-H2-V.create_node {…, type_id:"Utilitaires|ContrôleDeFlux|Branche", pos:{x:<x231>+250, y:<y231>}}→ G-H2-B.break_pins: sortie_231.then(index relu, attendu 0) / entrée_207.Exec(index relu, attendu 0).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,selfcaché) ;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).
get_node_infosdes 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 deSTE-H3-SPEC.md:60-64).
Comportement :
- Corps présent :
IsValidvrai →_207comme 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 deuxArray_Clearrestent avant la garde. Identique (établi pour les nœuds à effet ; estimé fort pour la macroForEachLoopà 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) :
SetMorphTargetrange la valeur dansMorphTargetCurvessans exiger d'asset (UERuntime\Engine\Private\Components\SkeletalMeshComponent.cpp:4011-4035) ; seulClearMorphTargetsla vide (:4037-4040; GrepMorphTargetCurves\.(Empty|Reset)sur ce fichier → 1 ligne) ;SetSkinnedAssetAndUpdate→SetSkeletalMesh(:3595-3598). Les morphs posés avant le corps survivraient donc à son affectation (à condition queGenerateAndApplyFloats, non dumpé, passe parSetMorphTarget). 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)
.\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 <EG avant> <EG après> <profil>sans autre écart.hyper_blueprint_debug_inspectEventGraph = 0 diagnostic ;CompileAllBlueprintséditeur fermé : 0 erreur (lp-validate).- 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 etDestroyDummy was from CheckScenes> 0, sinonBLOCKED(non mesuré) (L2 : 123 / 185) ;property CallFunc_GetSkeletalMesh_ReturnValue(count) : pas 0 attendu, resteCharGen/SavePreset _3si 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).
- 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_ReturnValueattendu à 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:32et la jonction 5.8:32) : section DIAG identique.