38 KiB
Fiche STE-H3g — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-sonnet-5-5 — Agent lp-cart — Skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths (préchargés), lp-mcp (lu : § 1, 3, 4), lp-doc (gabarits)
STE-H3g-SPEC : gardes G15 (GI.isSameAs, variante b) et G16 (GI:EventGraph, UpdateDefaultHair, F3 sans changement de comportement) (2026-10-13)
Lecture seule stricte (Read, Grep, Glob ; un Bash : git rev-parse, md5sum, git status, git log). Aucun éditeur, MCP, commandlet, build ni jeu.
Copie D:\Game\LifePlay-master 5.8, branche ue5.8, HEAD 0745917. Gabarit SPEC : D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md:36-38.
Suite de STE-H3-SPEC.md (G1-G6), STE-H3b-SPEC.md (G7-G11), STE-H3c-SPEC.md (G12), STE-H3d-SPEC.md (G13) : mêmes conventions (H3S § 2.1),
gardes G15 et G16, risques et questions préfixés H3g. Correspondance avec la rubrique 7 de la fiche : § 2.2 = G15, § 2.3 = G16, § 3 = repli,
§ 4 = risques et questions, § 5 = interdits, § 7 = critères de VAL.
Statut : complet et exécutable (aucun dump manquant). Deux points à lire avant toute IMPL :
- G15 : la variante b ne change pas « un seul cas ». La fiche (rubrique 3) et la décision Q-H3e-1 supposent que seul « les deux personnes vides » change.
Le corps 5.8 de
GetVariable(GVP) montre trois cas changés, dont (vide,[None]) qui est le chemin « pas de compagnon » : § 2.2.4, Q-H3g-1 (décision de l'utilisateur avant IMPL de G15). - G15 : le DIAG H3e § 2 est réfuté sur un point :
CurrentCompanionne rend jamais un tableau vide (GVP:105-112 + 68-70 :[Companion], longueur 1, éventuellement[None]). L'opérande vide des 2 lignes HJ:16827 / 16841 est donc la cible (Actor/Companion) ou, pourpimp_out, le nom mal orthographié (§ 2.2.4). Le résultat actuel deisSameAssans compagnon n'est pas « faux » mais « vrai » quand la cible est elle aussi absente.
Abréviations (chemins absolus) :
- D =
D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\(dumps du jour, GIC4817B84) : ISA = DGI__isSameAs_P2.txt(11 nœuds, 70 lignes) ; GVP = DGI__GetVariable_P2.txt(53) ; GEV = DGI__EventGraph_P2.txt(6 249) ; ACT = DAction__EventGraph_P2.txt(363) ; CRBG = DGI__CallFunctionReturnBool_P1.txt(591 nœuds, avant G13) ; les dumps-Pins -Execont deux listings (CRBG:1 puis CRBG:2997 ; GEV:1 puis GEV:30177 : le second ne montre que l'exécution) ; - HJ =
D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-H3e-J\LifePlay.log(copie du journal H3e ; ligne de commande-ScriptStackOnWarnings -LPHitchLog, HJ:291) ; - H3eD =
STE-H3e-DIAG.md, H3fD =STE-H3f-DIAG.md, H3dS / H3dI =STE-H3d-SPEC.md/STE-H3d-IMPL.md, H3S =STE-H3-SPEC.md(dansetat\ST\STE\) ; - UE =
D:\Game\Launcher\Engine\UE_5.8\Engine\Source\(moteur installé, lu).
État relevé (Bash, lecture) : git rev-parse --short HEAD → 0745917 ; md5sum Content/BPs/GI.uasset → c4817b847ecf246c859647577647d6e9 (= C4817B84 des dumps) ;
git status --short -- Content/BPs/GI.uasset Content/BPs/Person.uasset Content/BPs/Room.uasset → M Content/BPs/Person.uasset, M Content/BPs/Room.uasset (GI non modifié : conforme à la fiche) ;
git log --oneline -1 -- Content/BPs/GI.uasset → 0745917 (G13).
1. Objectif mesurable
Supprimer, sans autre changement de comportement que ceux nommés, deux familles de lignes du journal H3e, par deux gardes dans Content\BPs\GI.uasset :
- G15 (
isSameAs) : les 2 lignesScript Msg: Attempted to access index 0 from array 'Person_24_…' of length 0 in '/Game/Structs/Variable.Variable'!(HJ:16827, 16841), toutes deux attribuées àGI.isSameAspar leur pile (HJ:16828-16836, 16842-16850 ; H3eD § 1). Variante b décidée (Q-H3e-1) :Same = falsedès qu'un des deux tableauxPersonest vide, avant la lecturePerson[0]. - G16 (F3) : les 2 lignes
Accessed None trying to read (real) property SkeletalMesh_2_93573D9945CD4136090CCBA1F1188DAC in not an UClass(HJ:16058, 16086 ; pileContact > OnClickContactButton > GI.LoadPreset > GenerateAndApplyStrings > PlayerChangeRace > UpdateDefaultHair, HJ:16061-16071), sans changement de comportement (décision Q1 de l'utilisateur).
Attendus mesurables (§ 7) : isSameAs 11 → 15 nœuds ; GI:EventGraph 6 249 → 6 253 ; GetVariable (53), Action:EventGraph (363), CallFunctionReturnBool (G13) inchangés ;
journal de jeu comparable : Person_24_.*of length 0 2 → 0, SkeletalMesh_2_93573D 2 → 0, isSameAs 3 → 0, UpdateDefaultHair 4 → 0 (piles ; HJ), LogScript: Warning: Accessed None 13 → ≤ 11 (indicatif).
1.1 Faits établis sur ISA (GI.isSameAs, 11 nœuds)
| Nœud | Liens (ISA) |
|---|---|
FunctionEntry_3241 |
then → CallFunction_23441.execute (ISA:3) ; Person → CallFunction_23441.Name, Concat_1059.A (4) ; Person2 → CallFunction_23442.Name, Concat_1059.C (5) |
CallFunction_23441 (GetVariable, appel 1) |
execute ← CallFunction_5124.then, FunctionEntry_3241.then (7) ; then → CallFunction_23442.execute (8) ; Name ← Entry.Person (10) ; Variable → BreakStruct_721.Variable (11) |
CallFunction_23442 (GetVariable, appel 2) |
execute ← CallFunction_23441.then (13) ; then → FunctionResult_8.execute (14) ; Name ← Entry.Person2 (16) ; Variable → BreakStruct_722.Variable (17) |
BreakStruct_721 / _722 (BreakVariable) |
Person_24_4B712C904750AD4CE1580DBEBD9E7396 → GetArrayItem_2719.Array (26) / GetArrayItem_2720.Array (36) ; autres sorties sans lien |
GetArrayItem_2719 / _2720 (lecteurs Person[0]) |
Array ← BreakStruct ; Dimension 1 = '0' ; Output → CallFunction_23443.A (38-41) / .B (42-45) |
CallFunction_23443 |
KismetMathLibrary::EqualEqual_ObjectObject : A ← 2719, B ← 2720, ReturnValue → FunctionResult_8.Same (46-50) |
FunctionResult_8 |
execute ← CallFunction_23442.then (seule source) ; Same ← CallFunction_23443.ReturnValue (51-53) |
Concat_1059 → PrintString_5124 |
PrintString_5124.execute (vide) (61) : inatteignable (aucune entrée d'exécution) ; son then → CallFunction_23441.execute (62) |
Conséquences : (i) un seul FunctionResult (FunctionResult_8) : aucun à créer ni supprimer ; (ii) les nœuds purs (BreakStruct, GetArrayItem, EqualEqual) sont évalués à
l'exécution de FunctionResult_8, donc après CallFunction_23442.then ; (iii) le PrintString « is Same as » ne s'exécute pas, ce qui explique is Same as = 0 dans HJ (H3eD § 1 : « non vérifié sans dump », maintenant établi).
1.2 Longueur du tableau Person rendu par GetVariable (GVP) : d'où vient « vide »
- Noms du
Switch(GVP:39-54) :Player→[PlayerCharacter](98-104 ; 57-72) ;CurrentCompanion→[Companion](105-112 ;MakeArray_1506.[0]←VariableGet_36005.Special, 68-70) : longueur 1,[None]sans compagnon ;A1…A5→[SexPersons[i]](145-192) ;Ptr(193-200) ;Interacting(229-236) ;choiceetHoursElapsed→ un tableauPersond'un élément vide[None](123-125 ; 211-213). - Tout autre nom :
Default→ForEachLoopWithBreaksurVariables(GVP:8-15, 40) ; trouvé → laVariablestockée est rendue (34, 36-38) ; introuvable →Completed (vide)(GVP:15) : la fonction se termine sansFunctionResult,Variablen'est pas écrite → tableaux vides (estimé : même mécanisme que § 2.2.3 ; cohérent avec « of length 0 in '/Game/Structs/Variable.Variable' », HJ:16827). - Donc un tableau vide ne vient que de ce dernier cas (nom absent de
Variables, ou variable stockée sans personne).CurrentCompanionn'est jamais vide.
1.3 Appelants de isSameAs (demandé par la fiche, rubrique 4)
- Site connu, seul dans les dumps 5.8 :
CallFunction_26270dansCallFunctionReturnBool, casSwitchString_77.isSameAs(CRBG:21, 409-426) :Person←VariableGet_11335.ActualVariable,Person2←GetArrayItem_3010.Output(ActualParameters[0]),Same→VariableSet_7609.BoolToReturn→IfThenElse_8994(CRBG:413-426). GrepisSameAssur CRBG → 3 lignes : 21, 409, 3017 (3017 = pin du second listing du mêmeSwitchString_77). - Grep
isSameAs→ 0 sur GEV, ACT,GI__CallFunction_V5.txt,PC__EventGraph.txt,Person__EventGraph_D4.txt,Room__EventGraph_V5.txt; sur le dump 5.5 complet de GI (etat\portage55\L6f\L6f_dump_GI.txt) : 2 lignes seulement, l'appel (21167) et la définition (25963). - Binaires
.uasset, fichier nommé (isSameAs, mode count) :Person,Room,PC,StreetMap_Static,AI(Content\BPs\),UI\Action,UI\Scene→ 0 ; contrôle positif :GI.uasset→ 6 ;UI\Action.uassetcontientCheckConditionsWho(1) : la méthode voit un nom de fonction dans un.uasset. - Côté données (non modifié par cette SPEC) : 8
.lpactionenWHO:(H3eD § 2), dontapproach_bar_i_.lpaction:13(If !Actor.isSameAs(CurrentCompanion) && …) etpimp_out.lpaction:13(Actor.isSameAs(CurrentConpanion), nom mal orthographié) ;apply_sunscreen.lpscene:79(CurrentCompanion.isSameAs(Actor)) ; le reste = 89 occurrences dans 33 fichiers (H3eD C7, non recomptées ici). - Non couvert (incertain) : les autres Blueprints et widgets non listés ci-dessus. Ce qui trancherait :
find_nodes {graph, title:"isSameAs"}par MCP sur chaque graphe, ou la VAL (le dump après IMPL ne dit rien des appelants ; seul un appel inconnu àisSameAsverrait son résultat changer par b, cas du § 2.2.4).
2. Fichiers touchés, ordre
Un seul fichier : Content\BPs\GI.uasset, deux graphes : fonction isSameAs (G15) et EventGraph (G16, événement personnalisé UpdateDefaultHair).
Ni GetVariable, ni CallFunctionReturnBool, ni CheckConditionsWho/Other, ni Action, ni Person, ni le C++, ni les scènes, ni les modules. Aucune garde sur Person (STG-B1 y travaille).
Ordre : G15 puis G16, dans une seule session MCP, un seul save_assets à la fin (§ 3). G16 n'exige pas la réponse Q-H3g-1 : si Q-H3g-1 n'est pas rendue, exécuter G16 seule.
2.0 Préalables (dans l'ordre, avant tout create_node)
- P1 (dumps) : aucun manquant. Refaire les deux seulement si le MD5 de
GI.uassetà l'entrée de l'IMPL diffère dec4817b847ecf246c859647577647d6e9(autre IMPL passée entre-temps) :.\Tools\RunCommandlet.ps1 -Lot STE-H3g -Commandlet LPDumpFunction -Args '-BP=GI -Fn=isSameAs -Pins -Exec' -EtatDir "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE"et idem avec-Fn=EventGraph(éditeur fermé :.\Tools\UeState.ps1 -Kind Editeur= 0). - P2 : MD5 de
GI.uassetrelevé juste avant l'IMPL (attenduc4817b84…) = valeur de repli. - P3 : gel annoncé à toutes les sessions (lp-mcp § 1.7),
.\Tools\Snapshot.ps1 -Lot STE-H3g(GI.uasset), éditeur headless (lp-mcp § 2),AssetTools.load_asset/Game/BPs/GI. - P4 :
get_node_infos {nodes:[{refPath:"/Game/BPs/GI.GI:<Graphe>.K2Node_<Nom>"}]}pour chaque nœud existant cité ci-dessous (deux appels de dix nœuds au plus : H3dI § 5 « Constats ») ; les nomsK2Node_*des dumps sont les noms derefPath(H3dI § 1.1 : tous retrouvés) ; liens = colonne « Avant » ; relever les indices de broches (attendus :thensortie 0,Variablesortie 1 desGetVariable;Person_24_…sortie 6 des BreakStruct, H3dI:49-50 ;FunctionResult_8.executeentrée 0). Tout écart : arrêt et rapport.- G15 :
graph=/Game/BPs/GI.GI:isSameAs; nœudsFunctionEntry_3241,CallFunction_23441,CallFunction_23442,BreakStruct_721,BreakStruct_722,GetArrayItem_2719,GetArrayItem_2720,CallFunction_23443,FunctionResult_8,CallFunction_5124. - G16 :
graph=/Game/BPs/GI.GI:EventGraph; nœudsCustomEvent_5,IfThenElse_104,IfThenElse_65,IfThenElse_66,CallFunction_259,CallFunction_287,CallFunction_662,CallFunction_661,MacroInstance_5,MacroInstance_4,CallFunction_5654,CallFunction_237,BreakStruct_12,BreakStruct_13,VariableGet_1147,VariableGet_64.
- G15 :
- P5 : avant chaque
create_node,find_node_types {graph, type_id_filter, context_pins:[]}: letype_idattendu doit figurer dans la réponse (§ 2.1) ; après création,get_node_infosdu nouveau nœud. - P6 : Q-H3g-1 répondue (G15) ; G16 : Q1 déjà répondue.
2.1 Conventions (reprises de H3S § 2.1, H3dS § 2.1, lp-mcp § 3-4)
type_idrelus en 5.8 dans les IMPL précédentes : Branche =Utilitaires|ContrôleDeFlux|Branche(entréesexecute0,Condition1 ; sortiesthen0,else1 ; H3dI:69) ; IsValidIndex =Utilitaires|Tableau|IsValidIndex(TargetArrayentrée 0,IndexToTestentrée 1 =0par défaut,ReturnValuesortie 0 ; H3dI:68) ; IsValid =Utilitaires|IsValid(fonction pure,Objectentrée 0,ReturnValuesortie 0 ;STE-H5c-IMPL.md:24,STE-GARDES-IMPL.md:37-38).- Cascades de Branche, jamais AND / OR / Select purs ; une sortie de données peut alimenter plusieurs entrées (les liens existants restent).
delete_nodeinterdit. AucunFunctionResultcréé. Aucunset_pin_value(valeurs par défaut relues). - Après les deux gardes :
compile_blueprint {blueprint:{refPath:"/Game/BPs/GI.GI"}, warnings_as_errors:true}→null, journal de l'éditeur lu (aucune ligne Error/Warning liée à GI) ; puissave_assets {asset_paths:["/Game/BPs/GI"]}; fermeture de l'éditeur,UeState.ps1= 0, MD5 relevé (hyper_blueprint_debug_inspectabsent du serveur, H3dI:156). - Positions (
pos) libres, à côté du nœud amont, sans chevauchement ; effet visuel seulement.
2.2 G15 — isSameAs, lecteurs Person[0] (2 lignes HJ) — ISA
2.2.1 Avant → après
Avant : § 1.1. Chaîne d'exécution : FunctionEntry_3241.then → CallFunction_23441 → CallFunction_23442 → FunctionResult_8 ; Same = EqualEqual_ObjectObject(Person[0], Person2[0]).
Après : 4 nœuds créés (forme équivalente plus courte : aucune, un AND pur de deux IsValidIndex coûterait 4 nœuds aussi et contredit la convention) :
G15-I1 IsValidIndex (TargetArray ← BreakStruct_721), G15-B1 Branche, G15-I2 IsValidIndex (TargetArray ← BreakStruct_722), G15-B2 Branche.
break_pins (1) : CallFunction_23442.then (sortie 0) / FunctionResult_8.execute (entrée 0).
connect_pins (7) :
CallFunction_23442.then→G15-B1.execute(0) ;BreakStruct_721.Person_24_4B712C904750AD4CE1580DBEBD9E7396→G15-I1.TargetArray(0) (le lien versGetArrayItem_2719.Arrayreste ; la broche générique prend le typePersonau câblage, H3dI:82) ;G15-I1.ReturnValue(0) →G15-B1.Condition(1) ;G15-B1.then(0) →G15-B2.execute(0) ;BreakStruct_722.Person_24_4B712C904750AD4CE1580DBEBD9E7396→G15-I2.TargetArray(0) (le lien versGetArrayItem_2720.Arrayreste) ;G15-I2.ReturnValue(0) →G15-B2.Condition(1) ;G15-B2.then(0) →FunctionResult_8.execute(0).G15-B1.elseetG15-B2.else: aucun lien (le chemin « vide » s'arrête là, § 2.2.3).IndexToTestdes deux IsValidIndex reste à0(relu). Comptes :isSameAs15 nœuds (11 + 4) ; 1 coupure ; 7 liens. Inchangés à la relecture :Person/Person2→CallFunction_23441/2.Name,GetArrayItem_2719/2720(liensArrayetOutput),CallFunction_23443,FunctionResult_8.Same,Concat_1059,PrintString_5124.get_node_infosfinal :G15-B2.thenseul lien deFunctionResult_8.execute;CallFunction_23442.then→G15-B1.executeseul ;FunctionResult_8toujours seul.
2.2.2 Comportement
- Les deux tableaux ont au moins un élément :
then/then→FunctionResult_8; mêmes nœuds, mêmes entrées : résultat identique (établi par construction). - Au moins un tableau vide : l'exécution s'arrête à la Branche (aucune lecture
GetArrayItem: plus de ligneAttempted to access index 0 …) ;Samen'est pas écrit : § 2.2.3.
2.2.3 Pourquoi Same vaut false sur le chemin vide (estimé fort, pas établi par un essai)
isSameAs n'a qu'un FunctionResult, et créer un second est exclu (convention). Un chemin qui ne l'atteint pas laisse Same non écrit. Lecture du moteur :
une sortie d'une fonction script n'est pas copiée dans le cadre de l'appelé : l'adresse transmise est celle de la variable de l'appelant (ScriptCore.cpp:1033-1046, en particulier :1045) ;
cette variable est un temporaire de CallFunctionReturnBool (seul appelant, § 1.3), fonction non persistante dont le cadre est mis à zéro à chaque appel (ScriptCore.cpp:984-994) ;
CallFunction_26270 n'est exécuté qu'une fois par appel de CallFunctionReturnBool : le temporaire vaut donc false à la lecture par VariableSet_7609. La fin de fonction sans FunctionResult est un chemin normal du compilateur
(ReturnStatement empilé en adresse de retour, KismetCompilerVMBackend.cpp:2338-2349 ; KCST_EndOfThread → dépilement, :2238-2240 ; KCST_Return → EmitReturn, :2244-2246).
Le projet s'appuie déjà sur ce mécanisme : GetVariable (nom introuvable, GVP:15) et G3 (STE-H3-SPEC.md:108-110). À confirmer en VAL : compilation sans diagnostic (§ 7.2) ; repli de conception en § 3.
2.2.4 Cas changés (réponse à la demande « le seul cas changé » de la fiche : non confirmé, trois cas)
Avant : Same = (A0 == B0) avec A0 = Person[0] (ou None si le tableau est vide : KismetArrayLibrary.cpp:466-480, défaut ligne 479) et == = A == B (KismetMathLibrary.inl:2210-2213).
| # | Person (A) |
Person2 (B) |
Avant | Après G15 | Changé |
|---|---|---|---|---|---|
| 1 | ≥ 1, [0] valide (X) |
≥ 1, [0] valide (Y) |
X == Y |
X == Y |
non |
| 2 | ≥ 1, [0] valide |
≥ 1, [0] None (et symétrique) |
faux | faux | non |
| 3 | ≥ 1, [0] None |
≥ 1, [0] None |
vrai | vrai | non |
| 4 | vide | ≥ 1, [0] valide (et symétrique) |
faux | faux | non |
| 5 | vide | vide | vrai (None == None) |
faux | oui (cas visé par la fiche) |
| 6 | vide | ≥ 1, [0] None (ex. CurrentCompanion sans compagnon, § 1.2) |
vrai | faux | oui (non vu par la fiche) |
| 7 | ≥ 1, [0] None |
vide | vrai | faux | oui (symétrique de 6) |
Les cas 6 et 7 sont atteignables dès qu'un opérande est [None] (CurrentCompanion sans compagnon, choice, HoursElapsed, A1…A5 hors plage, Interacting / Ptr nuls : § 1.2) et l'autre absent de Variables. |
|||||
Sites de données exposés (non modifiés) : les 8 .lpaction (CurrentCompanion en Person2), apply_sunscreen.lpscene:79, pimp_out.lpaction:13 (Person2 = nom absent, donc toujours vide : cas 4/5/7, résultat inchangé sauf si la cible est aussi absente). |
|||||
Exemple lisible : approach_bar_i_.lpaction:13 (If !Actor.isSameAs(CurrentCompanion) && …) avec Actor absent et aucun compagnon : isSameAs passe de vrai à faux, donc la sous-condition !… de faux à vrai (les autres sous-conditions de la ligne ne sont pas analysées ici). |
|||||
Lequel de ces chemins a produit HJ:16827 / 16841 : incertain (2 clics sur 32 310 ACtualLEftMouse ; 8 actions candidates, aucune ne se lit dans la pile) ; ce qui trancherait : un journal avec la pile complète et l'identité de l'action évaluée (instrumentation, hors lecture seule). |
2.3 G16 — F3, UpdateDefaultHair, boucles Materials (2 lignes HJ) — GEV
2.3.1 Avant (GEV, dump du jour)
| Nœud | Liens (GEV) |
|---|---|
CustomEvent_5 UpdateDefaultHair |
then → IfThenElse_104.execute (3701-3703) |
IfThenElse_104 |
Condition ← VariableGet_3775.IsMale (3848-3850) ; then → IfThenElse_65 (homme) ; else → IfThenElse_66 (femme) (3704-3708) |
IfThenElse_65 / _66 |
Condition ← CallFunction_235 (EqualEqual_StriStri(Conv_TextToString(Race), 'Daz'), 19390-19394) ; then → CallFunction_662 / _661 (direct) ; else → CallFunction_259 / _287 (LoadHairMeshIfNeeded) (20352-20361) |
CallFunction_259 / _287 |
then → CallFunction_662.execute / _661.execute (19804-19808, 19826-19830) |
GetArrayItem_4 / _6 |
HairstyleTable[CallFunction_229 / _254] (SelectInt 50/24, 38/20) → BreakStruct_12 / _13 .SMBool (19812-19815, 19831-19834 ; 19633-19638, 19377-19382) |
BreakStruct_12 / _13 |
SkeletalMesh_2_93573D… → VariableGet_1147.self + CallFunction_662.NewMesh (19818) / VariableGet_64.self + CallFunction_661.NewMesh (19837) |
CallFunction_662 / _661 SkinnedMeshComponent::SetSkinnedAssetAndUpdate |
self ← VariableGet_241.HairSM ; NewMesh ← BreakStruct_12/13.SkeletalMesh_2 ; bReinitPose = 'true' ; then → MacroInstance_5.Exec / MacroInstance_4.Exec (3715-3726) |
MacroInstance_5 / _4 ForEachLoop |
Exec ← CallFunction_662.then / _661.then ; Array ← VariableGet_1147 / _64 (Materials, self ← BreakStruct_12/13.SkeletalMesh_2) ; LoopBody → CallFunction_2918 / _2917 (SetMaterial(HairSM, Array Index, Material)) ; Completed → CallFunction_5654.execute (4238-4251 ; 4143-4145, 4158-4160) |
CallFunction_5654 TurnMaterialDynamic(Mesh = HairSM) |
execute ← MacroInstance_4.Completed, MacroInstance_5.Completed ; then → CallFunction_237.execute (4138-4142) |
CallFunction_237 Person_C::FitMorphableHair |
execute ← CallFunction_5654.then (19399-19402) |
Les 8 occurrences de SkeletalMesh_2_93573D… dans GEV : 3719, 3725 (NewMesh), 4145, 4160 (self de Materials : les deux lectures qui lèvent F3, le message nomme la propriété du contexte, ScriptCore.cpp:3213-3216), 19818, 19837 (sorties des BreakStruct), |
|
11237 (valeur par défaut littérale d'une structure d'un autre nœud) et 28727 (MakeStruct_8, entrée vide de ClothTable) : les deux dernières ne sont pas des lectures. Les trois nœuds CallFunction_2918/_2917/_5654, CallFunction_237, LoadHairMeshIfNeeded ne sont pas touchés. |
2.3.2 Après : deux gardes (G16a homme, G16b femme), SetSkinnedAssetAndUpdate inchangé
Pour chaque garde : 2 nœuds (G16x-V IsValid pure, G16x-B Branche), 1 coupure, 5 liens.
- G16a (homme) —
break_pins:CallFunction_662.then/MacroInstance_5.Exec.connect_pins:BreakStruct_12.SkeletalMesh_2_93573D9945CD4136090CCBA1F1188DAC→G16a-V.Object(0) (les liens versVariableGet_1147.selfetCallFunction_662.NewMeshrestent) ;CallFunction_662.then→G16a-B.execute(0) ;G16a-V.ReturnValue(0) →G16a-B.Condition(1) ;G16a-B.then(0) →MacroInstance_5.Exec;G16a-B.else(1) →CallFunction_5654.execute(ce que l'exécution fait aujourd'hui après les boucles, sans le message).
- G16b (femme) —
break_pins:CallFunction_661.then/MacroInstance_4.Exec.connect_pins:BreakStruct_13.SkeletalMesh_2_…→G16b-V.Object;CallFunction_661.then→G16b-B.execute;G16b-V.ReturnValue→G16b-B.Condition;G16b-B.then→MacroInstance_4.Exec;G16b-B.else→CallFunction_5654.execute. Comptes :GI:EventGraph6 253 nœuds (6 249 + 4) ; 2 coupures ; 10 liens.CallFunction_5654.execute: 4 sources (MacroInstance_4.Completed,MacroInstance_5.Completed,G16a-B.else,G16b-B.else). Pourquoi deux gardes : la pile ne dit pas si le maillage None est celui de l'homme ou de la femme (HJ:16058, 16086 ; même pile) ; les deux lectures ont la même forme.
2.3.3 Comportement (identique ; une hypothèse VM estimée)
- Maillage valide :
IsValidvrai →then→ boucle : chemin d'origine inchangé (identique, établi par construction). - Maillage None, avant :
SetSkinnedAssetAndUpdate(HairSM, None); la boucle litMaterialssur un contexte nul :Accessed None, expression sautée et valeur rendue effacée (ScriptCore.cpp:3234-3245) → tableau vide, 0 itération (estimé fort : le macroForEachLoopn'est pas lisible dans les sources ; un seul message par appel, HJ:16058 / 16086, et non un par itération) →Completed→CallFunction_5654→CallFunction_237. - Maillage None, après :
IsValidfaux →else→CallFunction_5654→CallFunction_237: même suite d'exécution, sans lecture ni message.SetSkinnedAssetAndUpdate(HairSM, None)est conservé : la coiffure est toujours vidée (décision Q1 : « pas de coiffure, comme aujourd'hui »). - Écart théorique, sans effet visé : un maillage non nul mais détruit / en attente de destruction (
IsValidfaux,KismetSystemLibrary.h:126-128) ne serait plus parcouru ; une entrée deHairstyleTabletenue par référence dure n'est pas dans cet état (estimé). - Hors périmètre : la cause du None n'est pas instruite (H3fD § 4). Piste notée, non vérifiée : sur la branche
Daz(CallFunction_235vrai) lethenva directement àSetSkinnedAssetAndUpdatesansLoadHairMeshIfNeeded(GEV:20352-20361).
3. Repli
- Avant écriture : instantané
.\Tools\Snapshot.ps1 -Lot STE-H3g(P3), MD5 de départ (P2). - Aucun
save_assetstant quecompile_blueprintn'est pas à 0 diagnostic pour G15 et G16. Échec avant enregistrement : fermer l'éditeur sans enregistrer (le.uassetreste l'instantané), contrôler le MD5, rapporter. Aucun correctif improvisé. - Après enregistrement, lien relu différent,
type_idintrouvable, nœud inattendu : arrêt, rapport, puis sur décision de l'orchestrateur éditeur fermé :.\Tools\Restore.ps1 -Lot STE-H3g -Only Content\BPs\GI.uasset -WhatIf, puis sans-WhatIf; MD5 = valeur de P2. UnRestoredeGIdéfait toute autre écriture sur GI faite depuis l'instantané (IMPL jamais entrelacées). - Repli de conception de G15 (compilation avec diagnostic sur l'
elsenon relié, ou VAL montrantSame≠ faux sur tableau vide) : garde au site d'appel, dansCallFunctionReturnBool, entreSwitchString_77.isSameAsetCallFunction_26270(patron G13 variante b, H3dI § 1.2-1.3 :BoolToReturn = falseexplicite,IfThenElse_8994inchangé) : 2GetVariable(Scène|GetVariable) + 2 BreakStruct + 2 IsValidIndex + 2 Branche + 1 SetBoolToReturn≈ 9 nœuds. Non spécifié ici (fiche de suite : dumpCallFunctionReturnBoolà jour requis). - Après commit (gardien) :
git revertdu commit du ticket STE-H3g (indépendant de H3d / H10).
4. Risques R#, questions Q#
- R-H3g-1 : b change trois cas, pas un (§ 2.2.4) ; cas 6/7 = chemin « pas de compagnon » probable. → Q-H3g-1.
- R-H3g-2 : le
falsedu chemin vide de G15 repose sur la valeur initiale (zéro) du temporaire de l'appelant, mécanisme lu dans le moteur (§ 2.2.3) mais non rejoué ; un seul site d'appel, appelé une fois par cadre (établi, CRBG:409). Repli : § 3. - R-H3g-3 : déclencheur de la F2
isSameAsnon identifié (quelle action, quelle cible : 2 clics sur 32 en HJ) : un journal à 0 n'est un indice fort que si le parcours rejoue ce contexte ; la preuve principale est statique (dump) et moteur (§ 6). - R-H3g-4 : G16, boucle sur tableau effacé = 0 itération, estimée (macro non lisible) ; si l'exécution actuelle itérait, l'
elseomettrait desSetMaterialsans maillage : sans objet pour un maillage None (aucun matériau à poser). - R-H3g-5 : GI écrit entre-temps par une autre IMPL ou dumps périmés : P1 / P2 (MD5) ; Person et Room sont modifiés dans l'arbre par d'autres lots (
Mà l'état relevé) : ne pas les inclure au ticket. - R-H3g-6 : appelants inconnus de
isSameAs(§ 1.3, partie non couverte) : leur résultat changerait dans les cas 5-7 seulement. - R-H3g-7 : F3 masquée, pas guérie : le personnage garde une coiffure vide quand la table donne None (décision Q1) ; la cause reste ouverte.
- Q-H3g-1 (utilisateur, avant G15) : confirmer la variante b malgré les trois cas changés, ou choisir c (aucune garde, bruit : une ligne
Person_24par évaluation concernée, sansAccessed None, résultat actuel conservé). Rappel : une variante « rejeu exact » (a) est impossible dansisSameAs(secondFunctionResultexclu) et ne se réduit pas à une valeur constante (cas 5-7 vrai, cas 4 faux). Recommandation : b seulement si « un opérande absent donne faux » est le sens voulu deisSameAs; sinon c. - Q-H3g-2 (orchestrateur) : lieu de G15 :
isSameAs(retenu, 4 nœuds,elsenon relié) ou site d'appel (≈ 9 nœuds,falseexplicite). Recommandation :isSameAs. - Q-H3g-3 (orchestrateur) : BUILD-GO et ordre : G15 + G16 en une session GI, un seul instantané, un seul
save_assets. - Q-H3g-4 (utilisateur, facultative, hors fiche) : DIAG sur la cause du None de F3 (piste
Daz) et sur l'action évaluée à l'origine de la F2isSameAs;pimp_out.lpaction:13(CurrentConpanion) reste une donnée à corriger (Q-H3e-2).
5. Interdits
- Aucune modification de
GetVariable,CallFunctionReturnBool,CheckConditionsWho/Other,Action,LoadHairMeshIfNeeded,SetSkinnedAssetAndUpdate(appel conservé tel quel), des bouclesMacroInstance_4/_5, dePerson, du C++, des scènes et modules. - Aucune garde sur
Person(STG-B1). Aucun AND / OR / Select pur, aucunFunctionResultcréé, aucundelete_node, aucunset_pin_value. Pas d'IMPL sans P2 à P5. Pas d'écriture HyperAIStudio (lp-mcp § 1.1) ; outils Epic seulement. - Pas de commit par l'agent : ticket au gardien (
lp-repo). Pas detype_idnon relu ; pas de fusion avec une autre IMPL sur GI.
6. Valeurs moteur lues dans le moteur installé (UE = D:\Game\Launcher\Engine\UE_5.8\Engine\Source\)
GenericArray_Get, index invalide : messageAttempted to access index %d from array '%s' of length %d in '%s'!(propriété tableau, longueur, chemin du propriétaire = la structureVariable), puis élément remis au défaut :Runtime\Engine\Private\KismetArrayLibrary.cpp:459-482(message 472-478,ResetToDefault479).GenericArray_IsValidIndex: test sans message :Runtime\Engine\Private\KismetArrayLibrary.cpp:583-595.EqualEqual_ObjectObject=A == B:Runtime\Engine\Classes\Kismet\KismetMathLibrary.inl:2210-2213.UKismetSystemLibrary::IsValid(const UObject*): « non-null and not pending kill » :Runtime\Engine\Classes\Kismet\KismetSystemLibrary.h:126-128.Accessed None trying to read … property X in not an UClass(propriétaire non classe = structure) :Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3213-3216; contexte nul : expression sautée, valeur rendue effacée parClearValue::3234-3245.- Cadre d'une fonction script mis à zéro à chaque appel (non persistant) :
ScriptCore.cpp:984-994; paramètre de sortie = adresse de la variable de l'appelant :ScriptCore.cpp:1033-1046. - Fin d'une fonction Blueprint :
ReturnStatementempilé,KCST_EndOfThreaddépile,KCST_Returnémet le retour :Editor\KismetCompiler\Private\KismetCompilerVMBackend.cpp:2338-2349, 2238-2246; signature conservée même si des nœuds de retour sont élagués :Editor\KismetCompiler\Private\KismetCompiler.cpp:2447-2501. - Non lu (estimé) : le code du macro
ForEachLoop(StandardMacros, asset) ; la valeur initiale des temporaires de l'appelant (déduite deScriptCore.cpp:984-994).
7. Critères de VAL (commande → attendu)
- Dumps après IMPL (éditeur fermé,
UeState.ps1= 0), dans l'ordre :.\Tools\RunCommandlet.ps1 -Lot STE-H3g -Commandlet LPDumpFunction -Args '-BP=GI -Fn=isSameAs -Pins -Exec' -EtatDir "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE"→ en-tête=== GI :: isSameAs (15 noeuds) ===;CallFunction_23442.then→G15-B1.executeseul ;G15-B1.then→G15-B2.execute;G15-B2.then→FunctionResult_8.executeseul ;G15-B1.elseetG15-B2.elsesans lien ;G15-I1.TargetArray←BreakStruct_721.Person_24_…(qui gardeGetArrayItem_2719.Array),G15-I2.TargetArray←BreakStruct_722.Person_24_…;IndexToTest=0;FunctionResult_8.Same←CallFunction_23443.ReturnValueinchangé.... -Args '-BP=GI -Fn=EventGraph -Pins -Exec'→(6253 noeuds);CallFunction_662.then→G16a-B.executeseul ;MacroInstance_5.Exec←G16a-B.thenseul ;CallFunction_661.then→G16b-B.executeseul ;MacroInstance_4.Exec←G16b-B.thenseul ;CallFunction_5654.execute← 4 sources ;BreakStruct_12.SkeletalMesh_2_…→VariableGet_1147.self,CallFunction_662.NewMesh,G16a-V.Object(idem_13) ;NewMeshdes deuxSetSkinnedAssetAndUpdatetoujours relié.... -Fn=GetVariable→ 53 ;... -BP=Action -Fn=EventGraph→ 363 ;... -Fn=CallFunctionReturnBool→ 594 (591 + G13), inchangé par cette fiche.python Tools\DiffRemplacementNoeuds.py <avant = D\GI__isSameAs_P2.txt / D\GI__EventGraph_P2.txt> <après> <profil>: sans autre écart que les nœuds et liens ci-dessus. - Compilation :
compile_blueprint … warnings_as_errors:true→null, journal de l'éditeur sans Error / Warning pour GI (l'elsenon relié ne doit produire aucun diagnostic : sinon repli § 3) ;CompileAllBlueprintséditeur fermé : BP/Game/du projet 0 erreur (critère requalifié par l'utilisateur le 2026-10-13,CC_Shadersà part) ;LPTestStringCompat3120 / 0 / 60 (lp-validate). - Empreintes :
md5sum Content/BPs/GI.uassetrelevé et noté ;git status --short -- Content: GI ajouté auxMdéjà présents (Person,Room) sans les modifier (MD5 de ces deux fichiers identiques avant / après). - Journal de jeu (build Development empaqueté par BUILD, même ligne de commande que HJ :
-ScriptStackOnWarnings -LPHitchLog, HJ:291), Grep en modecount, baseline = HJ :Motif HJ Attendu Person_24_.*of length 02 0 isSameAs3 0 SkeletalMesh_2_93573D2 0 UpdateDefaultHair4 0 LogScript: Warning: Accessed None13 ≤ 11 (indicatif : −2 F3 ; les autres familles dépendent du parcours) Preuve que le parcours est rejoué (sinon un 0 ne prouve rien) : G16 = au moins 3 was from AddCreature(HJ : 3) puis au moins 1LOAD ETHNICaprès le troisième (HJ : 6 au total, dont 16085 et 16113) etCLICK ON THE SAME BUTTON≥ 1 (HJ:16114) ;G15 = clics de carte sur des personnes (HJ : 32 310 ACtualLEftMouse, dont les deux HJ:16825 et 16839 suivis d'une F2 en moins de 210 ms) ; limite R-H3g-3 : le déclencheur n'étant pas identifié, un 0 sur ce motif n'est qu'un indice. - Non-régression :
fatal|Unhandled|Critical|Assertion→ 0 ;CHECKING: .*pet/(horse/)?meet_new_person≥ 1 (HJ : 6) ;& .\Tools\LogStats.ps1 -Compare <journal>,<HJ>: aucune famille nouvelle ; G13 intact (isCreatureType→ 0). - Cas changés de G15 (R-H3g-1) : si Q-H3g-1 = b, la VAL note qu'un changement de résultat des conditions
isSameAssans compagnon est attendu (cas 5-7) ; aucun test de jeu ne le contrôle (non mesuré).
Fichiers à commiter (dépôt jeu, branche ue5.8, ticket au gardien) :
- A
Docs/Migration/etat/ST/STE/STE-H3g-SPEC.md
Tours : 27 / 50
Constats
Commandes (outils Grep / Read sauf mention) et comptes bruts :
- C1 Bash :
git rev-parse --short HEAD→0745917;md5sum Content/BPs/GI.uasset→c4817b847ecf246c859647577647d6e9;git status --short -- Content/BPs/GI.uasset Content/BPs/Person.uasset Content/BPs/Room.uasset→M Content/BPs/Person.uasset,M Content/BPs/Room.uasset. - C2 En-têtes : ISA:1
(11 noeuds); GVP:1(53 noeuds); GEV:1 et GEV:30177(6249 noeuds); ACT:1 et ACT:1901(363 noeuds); CRBG:1 et CRBG:2997(591 noeuds). - C3 Grep
SkeletalMesh_2_93573D9945CD4136090CCBA1F1188DACsur GEV (fichier nommé) → lignes 3719, 3725, 4145, 4160, 11237, 19818, 19837, 28727 (8) ;SetSkinnedAssetAndUpdate→ 997, 3715, 3721, 24129, 24162 (les trois derniers horsUpdateDefaultHair). - C4 Grep
isSameAs: CRBG → 21, 409, 3017 ; GEV, ACT,GI__CallFunction_V5.txt,PC__EventGraph.txt,Person__EventGraph_D4.txt,Room__EventGraph_V5.txt→ 0 ; L6f → 21167, 25963 ;.uasset:GI6,Person/Room/PC/UI\Action/BPs\AI/BPs\StreetMap_Static/UI\Scene0 ;UI\Action.uassetCheckConditionsWho→ 1. - C5
Content\Modules, Grep[A-Za-z0-9_]+\.isSameAs\([^)]*\)(-o) : appels listés (Actor / Companion / Dating / Agg / Host / Guest… ; 8.lpaction:CurrentCompanion×7,CurrentConpanion×1) ; Grep d'un dossier ignoré par git fonctionne ici (Content\Modulespassé enpath), contrairement àetat\…\dumps(fichier à nommer). - C6 HJ, Grep count :
310 ACtualLEftMouse→ 32 ;LOAD ETHNIC→ 6 ;was from AddCreature→ 3 ;isSameAs→ 3 ;UpdateDefaultHair→ 4 ;LogScript: Warning: Accessed None→ 13 ;SkeletalMesh_2_93573D→ 2 ;Person_24_.*of length 0→ 2 (HJ:16827, 16841) ; ligne de commande HJ:291. - C7 Lectures moteur : voir § 6 (toutes relues le 2026-10-13).
- C8 Écarts de méthode : un
Bash(git / md5sum) ; deuxGrepde dossier sans résultat (Contenten*.uasset: binaires arrêtés au premier octet nul, contrôle positif par fichier nommé) ; lecture deP1jusqu'à CRBG:3024 pour identifier le second listing. - Non mesuré : l'action évaluée à l'origine de HJ:16827 / 16841 ; le sexe du personnage de HJ:16058 / 16086 ; le comportement réel de
Samesur chemin non écrit (§ 2.2.3) ; la valeur deCurrentCompanion/ de la cibleActordans ces clics ; les autres Blueprints éventuellement appelants. - Lecture seule respectée ; un seul fichier écrit (ce livrable, non suivi). Aucun nom d'utilisateur ni chemin de profil cité.
Décisions de l'utilisateur (2026-10-13)
- Q-H3g-1 : c, pas de garde G15 (la variante b change un cas réel : sans compagnon,
CurrentCompanion=[None]) ; l'avertissementisSameAs(1 par clic carte, sans effet visible) est accepté comme bruit ; Q-H3e-1 = b est donc révisée en c. - G16 : approuvée pour l'IMPL (sans changement de comportement) ; IMPL par MCP sous instantané, VAL statique, commit ; preuve en jeu au prochain build.