Fiche STE-H3b — 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-H3b-SPEC : gardes F2 / F3 restantes du moteur de scènes (sites get / set / modifyActorVar, `_30`, `_41`) (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`. Gabarit SPEC : `D:\Game\LifePlay-master 5.8\.claude\skills\lp-doc\gabarits.md:36-38`. Suite de `STE-H3-SPEC.md` (gardes G1 à G6) : mêmes conventions (§ 2.1 de H3S), numérotation G7 et suivantes, R9 et Q4 et suivants. **Statut : partiel.** 4 gardes spécifiées (G7, G8, G10, G11) ; **G9 (`modifyActorVar`) arrêtée** : dump de la fonction absent (§ 2.4) ; G10 et G11 ont deux variantes, choix de l'utilisateur (Q4, Q5). Abréviations (chemins absolus) : - **D** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\` ; **CF** = D `GI__CallFunction.txt` ; **CRB** = D `GI__CallFunctionReturnBool.txt` (585 nœuds, STE-D2) ; **GAV** = D `GI__getActorVar.txt` ; - **H3S** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-H3-SPEC.md` ; - **L2** = `D:\Game\LifePlay_Build_5.8_STB-JEU2_Development\Windows\LifePlay\Saved\Logs\LifePlay.log` (journal STB-JEU2) ; - **AI** = `D:\Game\LifePlay-master 5.8\Content\Modules\vin_Base\AI\` (routines `.lpai`) ; - **UE** = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\` (moteur installé, lu). État du Blueprint : MD5 de `Content\BPs\GI.uasset` = `606eb43714c1f4317300b797edd02e62` (commande `md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/GI.uasset"`), égal à l'état STE-GI (`STE-TACHES.md:17`, « GI 606EB437… ») ; `git status --short -- Content/BPs/GI.uasset` = ` M` (STE-GI non commité) ; dernier commit du fichier `bc4d91f` (STC). Les dumps D datent de STE-D / STE-D2 (MD5 `d93785fc…`) ; STE-GI n'a touché que l'EventGraph (fiche § 3) : les nœuds de `CallFunction` et `CallFunctionReturnBool` cités ici sont ceux du GI actuel (estimé fort ; contrôle P4). STE-H3 (G1-G6) n'est pas encore implémentée (`STE-TACHES.md:19`). ## 1. Objectif mesurable Supprimer, sans changer ce que le jeu fait, les lectures restantes de personnage vide citées par Q3 de H3S (`H3S:196-198`, décision « oui, fiche à part », `STE-TACHES.md:32`). Référence L2, comptes Grep rejouables (§ Constats) : | Famille (motif Grep sur le journal de jeu) | L2 | Attendu après H3 + H3b | |---|---|---| | F3, `property CallFunc_Array_Get_Item_41 ` (`CallFunctionReturnBool`) | 14 | **0** (G11) | | F3, `property CallFunc_Array_Get_Item_30 ` (`CallFunction`) | 4 | **0** (G10 ; 4 si Q4 = c) | | F2 suivie d'un F1 dans `getActorVar` (multiligne, § Constats) | 71 | F2 du site supprimée (G7) | | F2 suivie d'un F1 dans `setActorVar` (multiligne) | 16 | F2 du site supprimée (G8) | | F2 suivie de `_41` ou de `Per` (multiligne) | 9 (7 + 2) | 2 (les 2 `Per` de `getRelatedPerson` restent, R13) | | F2 `Person_24_4B712C904750AD4CE1580DBEBD9E7396' of length 0` / `CHECKING:` | 351 / 2 318 | baisse de 94 lignes (71 + 16 + 7) à parcours égal, en plus de H3 | | `property Per in` (`getRelatedPerson`) | 4 | 4 (hors SPEC, R13) | Gardes : `CallFunction` site `getActorVar` (G7, 2 nœuds), site `setActorVar` (G8, 2 nœuds), site `modifyActorVar` (G9, **arrêtée**), commande `getUntil` (G10, `_30`, 7 nœuds en variante a) ; `CallFunctionReturnBool` commande `hasRelationship` (G11, `_41`, 6 nœuds en variante a). Total (variantes a) : **17 nœuds créés, 4 `break_pins`, 37 `connect_pins`, 3 `set_pin_value` (+ relectures), 0 suppression**. ## 2. Fichiers touchés, ordre Un seul fichier : `Content\BPs\GI.uasset`, graphes de fonction `CallFunction` (G7, G8, G10) et `CallFunctionReturnBool` (G11). Ni l'EventGraph, ni les fonctions appelées (`getActorVar`, `setActorVar`, `modifyActorVar`, `getRelatedPerson`, `hasRelationship`), ni le C++, ni le contenu des modules. Ordre : **après l'IMPL de STE-H3 (G1 à G6)**, puis G8, G7 (exige G2), G11, G10. G9 : non exécutable (§ 2.4). ### 2.0 Préalables (dans l'ordre, avant tout `create_node`) - P1. IMPL STE-H3 faite (G1-G6 compilées, enregistrées) ; réponses de l'utilisateur à Q4 (G10) et Q5 (G11). - P2. Dumps « avant » neufs des 2 graphes (GI a changé avec STE-GI puis changera avec H3) : `.\Tools\RunCommandlet.ps1 -Lot STE-H3b -Commandlet LPDumpFunction -Args '-BP=GI -Fn=CallFunction -Pins -Exec'` et `... -Args '-BP=GI -Fn=CallFunctionReturnBool -Pins -Exec'` ; MD5 de `GI.uasset` relevé (valeur de retour du repli). - P3. Gel annoncé (lp-mcp § 1.7), `.\Tools\Snapshot.ps1 -Lot STE-H3b` (GI.uasset), `AssetTools.load_asset` `/Game/BPs/GI`. - P4. Pour chaque nœud existant cité : `get_node_infos {nodes:[{refPath:"/Game/BPs/GI.GI:.K2Node_"}]}` ; les liens doivent égaler la colonne « avant » (§ 2.2 à 2.6) ; tout écart arrête la garde concernée. - P5. `type_id` hors lp-mcp § 3 (G10, G11 seulement), lus par `find_node_types {graph:{refPath:"/Game/BPs/GI.GI:"}, type_id_filter:"", context_pins:[]}` ; après `create_node`, `get_node_infos` doit montrer la classe et la fonction attendues, sinon arrêt et repli (§ 3) : | Nœud | Filtre | Attendu (estimé) | Contrôle `get_node_infos` | |---|---|---|---| | AddVariable (G10-A) | `AddVariable` | `Classe\|GI\|AddVariable` (forme lp-mcp § 3) | appel `GI_C::AddVariable`, broches comme `CallFunction_203` | | Map Find (G10-F, variante a) | `Map` puis `Find` | catégorie `Utilities\|Map`, nom `Find` (UE `Runtime\Engine\Classes\Kismet\BlueprintMapLibrary.h:48-49`), libellé localisé à lire | fonction `Map_Find`, pure | | Make Array (G10-M) | `Array` / `Tableau` | nœud `K2Node_MakeArray` (libellé localisé incertain) | classe `K2Node_MakeArray` | | hasRelationship (G11-H, variante a) | `hasRelationship` | `Classe\|GI\|hasRelationship` | appel `GI_C::hasRelationship`, **pur** (pas de broche `execute`) comme `CallFunction_177622` | | Set BoolToReturn (G11-S) | `BoolToReturn` | `Variables\|…\|DéfinirBoolToReturn` (lp-mcp § 3) | `K2Node_VariableSet` de la même variable que `VariableSet_57739` (`|Set`) | ### 2.1 Conventions communes Reprises à l'identique de H3S § 2.1 (`H3S:55-70`) : `type_id` Branch = `Utilitaires|ContrôleDeFlux|Branche`, IsValid = `Utilitaires|IsValid`, IsValidIndex = `Utilitaires|Tableau|IsValidIndex` ; indices de broches par direction (Branch entrée exécution 0 / Condition 1, sorties then 0 / else 1 ; VariableSet entrées exécution 0 / valeur 1, sortie then 0 ; **tous les autres relus par `get_node_infos`** avant `connect_pins`, valeurs attendues estimées : IsValid `Object` entrée 1, `ReturnValue` sortie 0 ; IsValidIndex `TargetArray` entrée 1, `Index` entrée 2, `ReturnValue` sortie 0) ; IsValidIndex `Index` laissé à `0` (relu) ; cascades de Branch, jamais AND / OR / Select purs ; une sortie de données peut alimenter plusieurs entrées, une entrée d'exécution peut recevoir plusieurs liens ; après chaque graphe : `compile_blueprint` + `hyper_blueprint_debug_inspect {blueprintAssetPath:"/Game/BPs/GI.GI", graphName:""}` = 0 diagnostic ; `save_assets {asset_paths:["/Game/BPs/GI"]}` à la fin. Ajouts H3b : nom complet de la broche du tableau = `Person_24_4B712C904750AD4CE1580DBEBD9E7396` (noté `Person_24_…`) ; broches génériques (wildcard) : câbler d'abord la broche typée (`G10-F.TargetMap` avant `Key` ; `G10-M.Array` vers `G10-A.Float` avant `[0]`), puis `set_pin_value` ; valeur littérale relue par `get_node_infos`. « Chemin non valide » (lp-mcp § 4) : refaire ce que l'exécution fait aujourd'hui avec la référence nulle. Avec un contexte nul, la VM saute l'expression et efface la valeur rendue (UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3234-3244`) : `Prefix` lu = `""`, `SaveSlot` lu = `0`, donc la clé `Prefix + Conv_IntToString(SaveSlot)` vaut `"0"` (établi). ### 2.2 G7 — site `getActorVar` de `CallFunction` (F2 : 71 dans L2) — CF Avant : `CallFunction_141` (`GetVariable`, cas `SwitchString_22.getActorVar`) `.then` → `CallFunction_142.execute`, `Variable` → `BreakStruct_40` (CF:4793-4798) ; `BreakStruct_40.Person_24_…` → `GetArrayItem_51.Array` (CF:4801-4809), index 0, `Output` → `142.Person` (CF:4811-4814) ; `142` = `getActorVar` (fonction de GI, `self` vide), `then` → `CallFunction_143.execute`, `Var` ← `GetArrayItem_52`, `Float` → `MakeArray_96.[0]` (CF:4815-4821) ; `143` = `AddVariable` (`Name` ← `VariableGet_103`, `Class = 'Float'`, `Float` ← `MakeArray_96`), `then` vide (CF:4828-4846). Après : IsValidIndex `G7-I`, Branch `G7-B` ; `break_pins` (`141.then`, `142.execute`) ; `connect_pins` (5) : `141.then` → `G7-B.execute` (0) ; `BreakStruct_40.Person_24_…` → `G7-I.TargetArray` ; `G7-I.ReturnValue` → `G7-B.Condition` (1) ; `G7-B.then` (0) → `142.execute` ; `G7-B.else` (1) → `143.execute`. Comportement : aujourd'hui, tableau vide → F2 (UE `Runtime\Engine\Private\KismetArrayLibrary.cpp:472-478`), `getActorVar(None)` rend, une fois G2 posée, `FunctionResult_1.Float = '0.0'` (GAV:125-127, H3S § 2.3), puis `143` écrit `[0.0]`. Après : `142` non exécuté, `143` lit la sortie `Float` de `142` jamais écrite dans ce cadre ; le cadre de `CallFunction` (fonction, pas l'ubergraph) est alloué et mis à zéro à chaque appel (UE `ScriptCore.cpp:984-993` ; cadre persistant réservé à l'ubergraph, UE `Runtime\Engine\Private\BlueprintGeneratedClass.cpp:1882-1897`) : `[0.0]`. **Identique à l'état après G2** (estimé fort) ; par rapport à l'état actuel (avant G2), identique si `ActorVars` n'a pas de clé `"0"` (R1 de H3S, déjà accepté par Q1 = A). Élément nul dans un tableau de longueur 1 : pas de F2, G2 le traite dans `getActorVar` (R14). ### 2.3 G8 — site `setActorVar` de `CallFunction` (F2 : 16 dans L2) — CF Avant : `CallFunction_150` (`GetVariable`, cas `setActorVar`) `.then` → `CallFunction_162.execute`, `Variable` → `BreakStruct_45` (CF:4851-4860) ; `BreakStruct_45.Person_24_…` → `GetArrayItem_63.Array` (CF:4867), index 0, `Output` → `162.Person` (CF:4869-4872) ; `162` = `setActorVar` (`Var` ← `GetArrayItem_64`, `Float` ← `GetArrayItem_65`), `then` vide (CF:4873-4879). Après : IsValidIndex `G8-I`, Branch `G8-B` ; `break_pins` (`150.then`, `162.execute`) ; `connect_pins` (4) : `150.then` → `G8-B.execute` ; `BreakStruct_45.Person_24_…` → `G8-I.TargetArray` ; `G8-I.ReturnValue` → `G8-B.Condition` ; `G8-B.then` → `162.execute`. `G8-B.else` : rien. Comportement : `setActorVar(None)` n'écrit rien, avant comme après G1 (H3S § 2.2, établi), et `162.then` est vide. Après : rien n'est appelé. **Identique** (établi). ### 2.4 bis G9 — complément de l'orchestrateur LOT STE (2026-10-10, après STE-D3) Dump STE-D3 `dumps\GI__modifyActorVar_D3.txt` (31 nœuds, code 0). `modifyActorVar(Person = None)` : `Prefix` et `SaveSlot` lus sur la personne vide (avertissements `Accessed None`), clé `Concat("" , "0")` = `"0"`, `Map_Find` puis boucle, puis `Map_Add(ActorVars, "0", …)` : **écriture parasite** de `ActorVars["0"]` (`CallFunction_1`, `CallArrayFunction_1`). Les 5 fonctions déjà dumpées sont identiques ligne pour ligne entre STE-D / STE-D2 et STE-D3 (GI 606EB437…) : les nœuds cités par cette SPEC restent valables. Forme retenue : celle de G8. IsValidIndex `G9-I`, Branch `G9-B` ; `break_pins` (`CallFunction_144.then`, `CallFunction_163.execute`) ; `connect_pins` (4) : `144.then` → `G9-B.execute` ; `BreakStruct_39.Person_24_…` → `G9-I.TargetArray` ; `G9-I.ReturnValue` → `G9-B.Condition` ; `G9-B.then` → `163.execute`. `G9-B.else` : rien (`163.then` est vide, CF:5143-5149). Comportement : avec une personne, identique ; sans personne, l'écriture `ActorVars["0"]` et les avertissements disparaissent. Décision de l'utilisateur (2026-10-10, dans la session LOT STE) : « Oui, poser G9 », par analogie avec Q2 (G5). L2 : 0 occurrence (Grep `GI_C:modifyActorVar` = 0). ### 2.4 G9 — site `modifyActorVar` de `CallFunction` — **arrêtée (dump manquant)** Structure relevée : `CallFunction_144` (`GetVariable`, cas `modifyActorVar`) `.then` → `CallFunction_163.execute`, `Variable` → `BreakStruct_39` (CF:5111-5120) ; `BreakStruct_39.Person_24_…` → `GetArrayItem_49.Array` (CF:5127), index 0, `Output` → `163.Person` (CF:5129-5132) ; `163` = `modifyActorVar` (`Var` ← `GetArrayItem_50`, `Float` ← `GetArrayItem_53`), `then` vide (CF:5143-5149). Forme probable : celle de G8 (IsValidIndex + Branch, `else` vide). Blocage : ce que fait `modifyActorVar(None)` (chemin « non valide ») ne se lit dans aucun dump (Glob `**/*modifyActorVar*` sous `etat\` : 0 fichier). Garde non spécifiée, sans improviser. Commande à joindre à un STE-D3 : `.\Tools\RunCommandlet.ps1 -Lot STE-D -Commandlet LPDumpFunction -Args '-BP=GI -Fn=modifyActorVar -Pins -Exec'`. Constat L2 : 0 lecture fautive dans `modifyActorVar` (Grep `GI_C:modifyActorVar` : 0 ligne) ; la F2 de ce site, si elle existe, n'est pas attribuable (le message F2 ne nomme pas la fonction). ### 2.5 G10 — commande `getUntil` de `CallFunction` (F3 `_30`, 4 dans L2) — CF, **variante selon Q4** Avant : `CallFunction_200` (`GetVariable`, cas `getUntil`) `.then` → `CallFunction_203.execute`, `Variable` → `BreakStruct_56` (CF:5674-5683) ; `BreakStruct_56.Person_24_…` → `GetArrayItem_78.Array` (CF:5690), index 0, `Output` → `VariableGet_150.self` (`Prefix`) et `VariableGet_151.self` (`SaveSlot`) (CF:5692-5701) → `Conv_IntToString` `201` et `Concat_StrStr` `CommutativeAssociativeBinaryOperator_3` → `CallFunction_202.Key` (CF:5702-5710) ; `202` = `Map_Find` **pur**, `TargetMap` ← `VariableGet_152.AI_Untils`, `Value` → `MakeArray_147.[0]` (CF:5711-5719) ; `203` = `AddVariable` (`Name` ← `VariableGet_153.VariableName`, `Class = 'Float'`, `Array = 'false'`, `Bool` ← `MakeArray_149`, `Float` ← `MakeArray_147`, `String` ← `MakeArray_148`, `Person` ← `MakeArray_146`, `Building` vide), `then` vide (CF:5720-5742). Pourquoi pas « `else` → `203` » comme G6 : l'entrée `Float` de `203` vient d'une chaîne **pure** (`Map_Find` ← `Concat` ← `Prefix` / `SaveSlot` ← `GetArrayItem_78`), réévaluée quand `203` s'exécute : les deux lectures fautives resteraient. Le chemin « non valide » doit donc rejouer `AddVariable` avec la clé `"0"`. Après, **variante a (rejeu exact)** : IsValidIndex `G10-I`, Branch `G10-B1`, IsValid `G10-V`, Branch `G10-B2`, Map Find `G10-F`, Make Array `G10-M`, AddVariable `G10-A` (7 nœuds) ; `break_pins` (`200.then`, `203.execute`) ; `connect_pins` (16) : 1. `200.then` → `G10-B1.execute` ; 2. `BreakStruct_56.Person_24_…` → `G10-I.TargetArray` ; 3. `G10-I.ReturnValue` → `G10-B1.Condition` ; 4. `G10-B1.then` → `G10-B2.execute` ; 5. `GetArrayItem_78.Output` → `G10-V.Object` ; 6. `G10-V.ReturnValue` → `G10-B2.Condition` ; 7. `G10-B2.then` → `203.execute` ; 8. `G10-B1.else` → `G10-A.execute` ; 9. `G10-B2.else` → `G10-A.execute` ; 10. `VariableGet_152.AI_Untils` → `G10-F.TargetMap` ; 11. `G10-F.Value` → `G10-M.[0]` ; 12. `G10-M.Array` → `G10-A.Float` ; 13. `VariableGet_153.VariableName` → `G10-A.Name` ; 14. `MakeArray_149.Array` → `G10-A.Bool` ; 15. `MakeArray_148.Array` → `G10-A.String` ; 16. `MakeArray_146.Array` → `G10-A.Person`. `set_pin_value` : `G10-F.Key` = `0` (chaîne) ; `G10-A.Class` = valeur relue sur `203` (`Float`) ; `G10-A.Array` = `false` (relire, ne poser que si le défaut diffère). `G10-A.then` : rien (comme `203`). Comportement (a) : aujourd'hui, personne nulle → `Until = [Map_Find(AI_Untils, "0").Value]`, soit la valeur de la clé `"0"` si elle existe, sinon la valeur remise à zéro (UE `Runtime\Engine\Private\BlueprintMapLibrary.cpp:35-55`, `ResetToDefault` l. 49) : 0.0. Après : même appel, même clé, même `AddVariable`. **Identique** (établi par construction). **Variante b (valeur neutre)** : sans `G10-F` (6 nœuds, 14 liens : sans 10 et 11), `G10-M.[0]` = `0.0`. Diffère de l'actuel quand `AI_Untils` a une clé `"0"` (Q4). **Variante c** : pas de garde (`_30` reste à 4 lignes). ### 2.6 G11 — commande `hasRelationship` de `CallFunctionReturnBool` (F3 `_41`, 14 dans L2 ; F2 : 7) — CRB, **variante selon Q5** Avant : cas `SwitchString_77.hasRelationship` → `CallFunction_177620` (`GetVariable`, CRB:162, 1267-1272), `Name` ← `VariableGet_128293.ActualVariable`, `.then` → `VariableSet_57739.execute`, `Variable` → `BreakStruct_6891` (CRB:1273-1276) ; `BreakStruct_6891.Person_24_…` → `GetArrayItem_12254.Array` (CRB:1283), index 0, `Output` → `VariableGet_128294.self` (`Prefix`) et `VariableGet_128295.self` (`SaveSlot`) (CRB:1285-1294) → `Conv_IntToString` `177621` et `Concat_StrStr` `CommutativeAssociativeBinaryOperator_16749` → `CallFunction_177622.Slot` (CRB:1295-1303) ; `177622` = `hasRelationship` **pur**, `Types` ← `VariableGet_128296.ActualParameters`, sortie `hasRelationship` → `VariableSet_57739.BoolToReturn` (CRB:1304-1310) ; `VariableSet_57739.then` → `IfThenElse_8994.execute` (CRB:1311-1315) ; `IfThenElse_8994` : `Condition` ← `Reverse`, `then` → `FunctionResult_0` (rend `!BoolToReturn`), `else` → `FunctionResult_1` (rend `BoolToReturn`) (CRB:300-302, 325-338). Même raison qu'en G10 : l'entrée de `VariableSet_57739` vient d'une chaîne pure, réévaluée ; le chemin « non valide » rejoue l'affectation. Après, **variante a (rejeu exact)** : IsValidIndex `G11-I`, Branch `G11-B1`, IsValid `G11-V`, Branch `G11-B2`, hasRelationship `G11-H`, Set BoolToReturn `G11-S` (6 nœuds) ; `break_pins` (`177620.then`, `VariableSet_57739.execute`) ; `connect_pins` (12) : 1. `177620.then` → `G11-B1.execute` ; 2. `BreakStruct_6891.Person_24_…` → `G11-I.TargetArray` ; 3. `G11-I.ReturnValue` → `G11-B1.Condition` ; 4. `G11-B1.then` → `G11-B2.execute` ; 5. `GetArrayItem_12254.Output` → `G11-V.Object` ; 6. `G11-V.ReturnValue` → `G11-B2.Condition` ; 7. `G11-B2.then` → `VariableSet_57739.execute` ; 8. `G11-B1.else` → `G11-S.execute` ; 9. `G11-B2.else` → `G11-S.execute` ; 10. `VariableGet_128296.ActualParameters` → `G11-H.Types` ; 11. `G11-H.hasRelationship` → `G11-S.BoolToReturn` (entrée 1) ; 12. `G11-S.then` (0) → `IfThenElse_8994.execute`. `set_pin_value` : `G11-H.Slot` = `0` (chaîne). Comportement (a) : aujourd'hui, personne absente → F2, `BoolToReturn = hasRelationship("0", Types)`, puis `IfThenElse_8994`. Après : même appel pur avec `Slot = "0"`, même variable, même suite. **Identique** (établi par construction). **Variante b (valeur neutre)** : sans `G11-H` (5 nœuds, 10 liens : sans 10 et 11), `G11-S.BoolToReturn` = `false` (relu). Identique seulement si `hasRelationship("0", …)` est faux (Q5). **Variante c** : pas de garde. ### 2.7 Correspondance nom compilé → nœud (`_30`, `_41`) Méthode de H3S § 2.8 (le suffixe `_N` ne se lit pas dans un dump) : empreinte dans L2 et contenu joué. - **`_41` = `GetArrayItem_12254` (`hasRelationship`)**, estimé fort : - empreinte : par événement, 1 F2 puis 2 lectures `_41` (L2:21375-21397), soit un `Array_Get` sur tableau vide dont la sortie est lue par 2 propriétés dans une seule évaluation ; dans CRB, 4 `GetArrayItem` ont une sortie vers 2 `self` (Grep `Output +-> K2Node_VariableGet_\d+\.self, K2Node_VariableGet_\d+\.self` : CRB:709, 738, 1288, 2522), soit `isSameGender` (`519`, `521`), `hasRelationship` (`12254`), `wantsBabies` (`34`) ; - moment : les 7 événements (14 lignes : L2:21377, 21388, 22906, 22917, 24344, 24355, 25676, 25687, 26940, 26951, 28458, 28469, 29515, 29526) tombent entre 21:32:11.093 et 21:32:21.141, dans la rafale du cas B (`AI` absente, H3S § 4 Q1) ; - contenu : dans `AI`, la seule condition `If` qui appelle une fonction booléenne sur une personne est `AI\high_priority\so_follows.lpai:7` `If AI.hasRelationship(Spouses, Dating)` (Grep `^\s*(If|Elseif|ElseIf)\b.*\b\w+\.\w+\(` sous `AI` : 7 lignes, les 6 autres sur `Dest`, `Current`, `Type`) ; les scènes vérifiées autour des 7 événements (lignes `CHECKING:` les plus proches, ex. L2:21374, 22893, 24336, 25663, 26935, 28449, 29512) n'appellent ni `hasRelationship`, ni `wantsBabies`, ni `isSameGender` (Grep `^(OTHER|WHO|WHERE):` sur ces `.lpscene`). - **`_30` = `GetArrayItem_78` (`getUntil`)**, estimé fort : - empreinte : par événement, 2 lectures `_30` **sans F2 propre** (L2:12087-12105, 22396-22414) : tableau de longueur 1 qui contient `None` ; la seule F2 précède les 2 `Per` de `getRelatedPerson` (L2:12063-12085, 22372-22394) ; - suite L2:22372-22438 = `so_follows.lpai:8-17` : `getRelatedPersonQuick` (F2 + 2 `Per` ; `CallFunction_175951`, son `Return` part dans `AddVariable` `12409` via `MakeArray_120.[0]`, CF:2431-2467, d'où `SO = [None]`), `SO.getUntil()` (2 `_30`), `SO.getBuilding(current)` et `SO.getBuilding(work)` (2 `_33`, L2:22416-22434), « [AI] 4 moves to (test0104) » (destination vide, L2:22436), « for 1.0 hours by 4 » (L2:22437), `SO.delete()` (« DestroyDummy was from callfunction Delete », L2:22438) ; l'événement de L2:12063-12106 s'arrête après `_30` (`Until - HoursElapsed > 1` faux) ; - candidats de même forme dans CF (Grep du motif ci-dessus : CF:2751, 5522, 5695, 6113, 7000) : `deletePerson` (`2368`), `setUntil` (`75`, = `_31`, H3S), `getUntil` (`78`), `setWantsBabies` (`99`), `setDatingID` (`144`) ; seul `getUntil` est dans `so_follows.lpai`. Statut : estimé fort ; la VAL tranche (comptes à 0, § 7). Écart possible : R9. ## 3. Repli - Avant écriture : instantané `.\Tools\Snapshot.ps1 -Lot STE-H3b` (P3) ; MD5 de départ relevé (P2). - Échec de compilation, diagnostic, `type_id` introuvable (P5) ou lien relu différent : arrêt, `.\Tools\Restore.ps1 -Lot STE-H3b` éditeur fermé (GI entier, retour à l'état après H3), puis contrôle MD5 = valeur de P2. - Après commit (gardien) : `git revert` du commit du ticket STE-H3b (n'annule pas STE-H3 si commits distincts). - Indépendance : G8, G10, G11 sont indépendantes entre elles et de H3 ; G7 exige G2 (sinon R1 de H3S s'applique). Une réponse c à Q4 ou Q5 retire seule la garde concernée. ## 4. Risques R#, questions Q# **Q4 (utilisateur) — G10, `getUntil` sur personne nulle** : (a) rejeu exact `Map_Find(AI_Untils, "0")` (7 nœuds, comportement identique) ; (b) `Until = 0.0` (6 nœuds) ; (c) pas de garde. Établi : la clé `"0"` d'`AI_Untils` est écrite aujourd'hui par `setUntil` avec `AI` vide (H3S § 2.6), écriture supprimée par G5 (Q2 acceptée, `STE-TACHES.md:29`) ; dans L2 elle existait à 21:32:11 (PNJ 4 envoyé suivre un partenaire inexistant vers une destination vide, L2:22436). (b) changerait ce cas (le PNJ ne bouge plus), ce qui ressemble à une correction mais n'est pas « comportement inchangé ». Avec G5 posée, (a) et (b) ne diffèrent plus que pour une sauvegarde qui contient déjà la clé `"0"` (essai : `LPDumpSave`, § 7.4). Recommandation : (a), conforme à Q1 = A. **Q5 (utilisateur) — G11, `hasRelationship` sur personne absente** : (a) rejeu exact `hasRelationship("0", Types)` (6 nœuds, identique) ; (b) `BoolToReturn = false` (5 nœuds) ; (c) pas de garde. Estimé : dans L2, aucune des 7 évaluations n'a été suivie d'un `getRelatedPerson` fautif (les 4 lignes `Per` sont à L2:12065-12078 et 22374-22387, hors des 7 événements), donc `hasRelationship("0", Spouses, Dating)` y a probablement rendu faux ; non établi sans dump de `hasRelationship` (clé `"0"` = joueur ? R1 de H3S). Recommandation : (a). **Q6 (orchestrateur) — dumps manquants (STE-D3)** : `.\Tools\RunCommandlet.ps1 -Lot STE-D -Commandlet LPDumpFunction -Args '-BP=GI -Fn=modifyActorVar -Pins -Exec'` (G9, bloquante) ; `... -Args '-BP=GI -Fn=getRelatedPerson -Pins -Exec'` (R13 : F2 + `Per`, 2 événements, 4 lignes) ; `... -Args '-BP=GI -Fn=hasRelationship -Pins -Exec'` (Q5 b seulement). - **R9** : correspondances `_41` → `GetArrayItem_12254` et `_30` → `GetArrayItem_78` estimées (§ 2.7). Autres candidats : `isSameGender` (`519`, `521` : un seul des deux absent donne aussi 1 F2 + 2 lectures), `wantsBabies` (`34`) ; `deletePerson`, `setWantsBabies`, `setDatingID`. Si `_41` ou `_30` reste > 0 en VAL : rapport, pas de correctif improvisé. - **R10** : G7 repose sur la sortie non écrite de `142` (cadre mis à zéro, § 6) et sur G2 posée avant (ordre § 2). - **R11** : `type_id` d'`AddVariable`, `Map_Find`, `MakeArray`, `hasRelationship`, `Set BoolToReturn` hors lp-mcp § 3 (P5) ; broches génériques de `Map_Find` et `MakeArray` typées au câblage ; échec → arrêt et repli, pas de `delete_node`. - **R12** : masquage : les variantes a gardent les comportements du chemin nul (PNJ envoyé vers une destination vide, `hasRelationship` du créneau `"0"`), liés au cas B (H5) et au retour nul de `getRelatedPerson`. - **R13** : non couvert : F2 + 2 `Per` dans `getRelatedPerson` (L2:12063-12085, 22372-22394, fonction sans dump) ; autres lecteurs de `Person[0]` (R7 de H3S). - **R14** : G7 et G8 n'ajoutent pas d'`IsValid` : l'élément nul d'un tableau de longueur 1 ne lève pas de F2 au site et est traité dans la fonction appelée (G2, G1). ## 5. Interdits - Aucune modification de l'EventGraph, des fonctions appelées (`getActorVar`, `setActorVar`, `modifyActorVar`, `getRelatedPerson`, `hasRelationship`), du C++ ; G9 non posée sans dump et complément de SPEC. - Aucune correction de contenu (`Content\Modules\…\*.lpai`, `.lpscene`), aucun nœud AND / OR / Select pur, aucun `FunctionResult` créé, aucun `delete_node`. - Pas d'écriture HyperAIStudio (lp-mcp § 1.1) ; outils Epic seulement ; pas d'IMPL sans P1 à P5. - Pas de commit par l'agent : ticket au gardien (lp-repo). ## 6. Valeurs moteur lues dans le moteur installé (UE = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\`) - Message F1 / F3 « Accessed None trying to read (real) property X in not an UClass » : UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3213-3216` ; contexte nul : expression sautée, valeur rendue effacée (`ClearValue`), `:3234-3244`. - Cadre d'une fonction script : alloué puis mis à zéro s'il n'est pas persistant, `ScriptCore.cpp:984-993` ; cadre persistant seulement pour la fonction ubergraph : `Runtime\Engine\Private\BlueprintGeneratedClass.cpp:1882-1897`. - Message F2 « Attempted to access index %d from array '%s' of length %d » : `Runtime\Engine\Private\KismetArrayLibrary.cpp:472-478` ; `GenericArray_IsValidIndex` sans message : `:583-589`. - `Map_Find` pur, catégorie `Utilities|Map`, nom affiché `Find` : `Runtime\Engine\Classes\Kismet\BlueprintMapLibrary.h:48-49` ; clé absente → valeur remise au défaut : `Runtime\Engine\Private\BlueprintMapLibrary.cpp:35-55` (l. 47-50). - Suffixe `_N` et ordre de compilation : repris de H3S § 6 (`KismetCompilerMisc.h:287-302`, `KismetCompiler.cpp:3918-3927`, `EdGraphCompiler.cpp:244-249`), non relus ici. ## 7. Critères de VAL (commande → attendu) 1. Dumps après IMPL (`.\Tools\RunCommandlet.ps1 -Lot STE-H3b -Commandlet LPDumpFunction -Args '-BP=GI -Fn= -Pins -Exec'`) comparés à P2 : `CallFunction` +11 nœuds (G7 2, G8 2, G10a 7 ; +10 si Q4 = b, +4 si c ; base H3 : 1 550, `H3S:258`) ; `CallFunctionReturnBool` 585 → 591 (Q5 = a), 590 (b), 585 (c) ; liens du § 2 relus ; `python Tools\DiffRemplacementNoeuds.py` sans autre écart. 2. `hyper_blueprint_debug_inspect` 0 diagnostic pour `CallFunction` et `CallFunctionReturnBool` ; `CompileAllBlueprints` éditeur fermé : 0 erreur (lp-validate). 3. Journal de jeu (build Development, parcours STB-JEU2, `STD-SCN-DIAG.md:198-206`), Grep en mode `count` : - `property CallFunc_Array_Get_Item_41 ` → **0** (L2 : 14) ; - `property CallFunc_Array_Get_Item_30 ` → **0** (L2 : 4 ; inchangé si Q4 = c) ; - multiligne `of length 0[^\n]*\r?\n[^\n]*\r?\n[^\n]*property (CallFunc_Array_Get_Item_41|Per) in` → nombre de lignes `property Per in` / 2 (L2 : 9 = 7 + 2) ; - `Person_24_4B712C904750AD4CE1580DBEBD9E7396' of length 0` / `CHECKING:` → inférieur au rapport mesuré en VAL de H3, de l'ordre de 94 / 2 318 = 0,041 à parcours égal (L2 : 351 / 2 318 = 0,151 avant H3) ; - `property Per in` → information (L2 : 4, R13) ; - `moves to \(test0104\)` → information (L2 : 1, L2:22436) : présent possible en variante a, absent attendu en b ; - `hours by [1-9]` → ≥ 1 si le parcours contient des routines de PNJ valides (L2 : 7), non-régression de l'IA ; - dénominateur `CHECKING:` (L2 : 2 318) ; `index SPAWN` > 0 avant de mesurer (lp-validate § 5). 4. Q4 / Q5 : `LPDumpSave -File= -Out=` (lp-commandlets) avant / après : clés `"0"` d'`AI_Untils` et d'`ActorVars` relevées (même essai que H3S § 7.4). Fichiers à commiter (dépôt jeu, branche `ue5.8`, ticket au gardien) : - A `Docs/Migration/etat/ST/STE/STE-H3b-SPEC.md` Tours : 33 / 60 ## Constats - Comptes rejoués sur L2 (Grep) : `CallFunc_Array_Get_Item_(30|41) ` → 4 et 14 lignes (contenu, numéros cités § 2.7) ; multiligne `Function /Game/BPs/GI\.GI_C:CallFunction\r?\n\s+Function /Game/BPs/GI\.GI_C:(getActorVar|setActorVar|modifyActorVar)\r?\n` → 174 ; multiligne `Function /Game/BPs/GI\.GI_C:\w+\r?\n\s+Function /Game/BPs/GI\.GI_C:getActorVar\r?\n` → 142 ; donc `getActorVar` 142 (toutes depuis `CallFunction`) + `setActorVar` 32 (H3S:72) + `modifyActorVar` 0 = 174 ; `GI_C:modifyActorVar|GI_C:getUntil|GI_C:hasRelationship` → 0 ; `GI_C:(modifyActorVar|getRelatedPerson):` → 4 (les 4 `Per`). - F2 suivie d'une lecture fautive (multiligne `Script Msg: Attempted to access index 0 from array 'Person_24_…' of length 0[^\n]*\r?\n[^\n]*\r?\n[^\n]*property (Person|Per|CallFunc_Array_Get_Item_\d+) in`) → 285 sur 351 ; dont suivie d'un F1 `getActorVar` → 71, d'un F1 `setActorVar` → 16, de `_41` ou `Per` → 9. - `getRelatedPerson` reçoit un **nom** de variable (`Person` ← `VariableGet_127193.VariableGet`, CF:2435, 2441-2442 ; `VariableGet` est une chaîne, `VariableSet_163.VariableGet` ← `Split.LeftS`, CF:452-456) : la F2 et les `Per` naissent dans la fonction, sans dump. - Grep de `etat\…\dumps` : passer `glob` `*__*.txt` (les `.txt` y sont ignorés par git) ; un motif finissant par `$` ne trouve rien dans ces dumps (fins de ligne CRLF) : essai de cette fiche. - Écart de chemin : aucun ; journal et gabarits aux chemins DOCMV (`D:\Game\LifePlay-master 5.8\Docs\Journal_Modifications.md`, `D:\Game\LifePlay-master 5.8\.claude\skills\lp-doc\gabarits.md`) ; journal non relu (aucune entrée nécessaire ici).