LifePlay-Revival/Docs/Migration/etat/ST/STE/STE-H2-DIAG.md
edujiro5972 8084622450 LifePlay UE 5.8 : état de développement au 2026-10-19
Tickets : REP1-T4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 22:57:42 +02:00

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 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<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 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_<Nom>"}]} 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:<x231>+250, y:<y231>+150}} → G-H2-V.
  2. create_node {…, type_id:"Utilitaires|ContrôleDeFlux|Branche", pos:{x:<x231>+250, y:<y231>}} → 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 <EG avant> <EG après> <profil> 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.