LifePlay-Revival/Docs/Migration/etat/ST/STE/STE-H3-SPEC.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

290 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Fiche STE-H3 — 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-H3-SPEC : gardes F1 / F3 des lectures vides de personnages du moteur de scènes (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`.
**Statut : partiel.** 6 gardes spécifiées (G1 à G6) ; la 4e lecture F3 (`CallFunc_Array_Get_Item_41`) n'est **pas dans
`CallFunction`** mais dans `CallFunctionReturnBool`, fonction sans dump 5.8 : écart au DIAG, rapporté sans improviser (§ 4, É1).
G5 dépend de Q2 ; toute l'IMPL dépend de Q1.
Abréviations (chemins absolus) :
- **D** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\` (dumps 5.8 après STC, `STE-D-INV.md`) ;
- **RSN** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STA\dumps\GI__ReturnStatIfNeeded.txt` (dump STA, sans en-tête, antérieur à STB et STC) ;
- **EGB** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STB\dumps\GI__EventGraph.txt` (antérieur à STC ; à reconfirmer sur STE-D2) ;
- **L2** = `D:\Game\LifePlay_Build_5.8_STB-JEU2_Development\Windows\LifePlay\Saved\Logs\LifePlay.log` (journal STB-JEU2 du DIAG) ;
- **JM** = `D:\Game\LifePlay-master 5.8\Docs\Journal_Modifications.md` (déplacé par DOCMV, CHG-300 : JM:1135) ; modèle de garde CHG-282 (JM:1153), CHG-283 (JM:1152) ;
- **UE** = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\` (moteur installé, lu).
État du Blueprint : MD5 de `Content\BPs\GI.uasset` = `d93785fca90e3342575fc0b601cc2e93` (commande
`md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/GI.uasset"`), égal à celui relevé à la fin de STE-D (`STE-D-INV.md:84`) ;
`git status --short -- Content/BPs/GI.uasset` vide ; dernier commit `bc4d91f` (STC, CHG-296). Les dumps **D** décrivent
donc le GI actuel (établi).
## 1. Objectif mesurable
Supprimer, sans changer ce que le jeu fait, les avertissements des lectures de personnage vide dans GI, sur le parcours
de STB-JEU2 rejoué (L2 comme référence, comptes par Grep, rejouables) :
| Famille (motif Grep sur le journal de jeu) | L2 | Attendu après IMPL |
|---|---|---|
| F1 ciblée, multiligne `property Person in not an UClass\r?\n[^\n]*\r?\n\s+Function /Game/BPs/GI\.GI_C:(getActorVar\|setActorVar\|ReturnStatIfNeeded):` | 267 | **0** |
| F3 de `CallFunction`, `property CallFunc_Array_Get_Item_3[123] ` | 153 (`_31` 96, `_32` 24, `_33` 33) | **0** |
| F3 de `CallFunctionReturnBool`, `property CallFunc_Array_Get_Item_41 ` | 14 | inchangé (hors SPEC, É1) |
| F2, `Person_24_4B712C904750AD4CE1580DBEBD9E7396' of length 0` | 351 | en baisse par `CHECKING:` (résidu attendu, § 4 R7) |
Gardes spécifiées par fonction : `setActorVar` 1 (G1), `getActorVar` 1 (G2), `ReturnStatIfNeeded` 1 en cascade de
2 branches (G3), `CallFunction` 3 en cascade (G4 `setCurrentLocation`, G5 `setUntil`, G6 `getBuilding`),
`CallFunctionReturnBool` 0 (bloquée, É1). Total : 20 nœuds créés, 7 `break_pins`, 42 `connect_pins`, 0 suppression.
## 2. Fichiers touchés, ordre
Un seul fichier : `Content\BPs\GI.uasset` (graphes de fonction `setActorVar`, `getActorVar`, `ReturnStatIfNeeded`,
`CallFunction` ; ni l'EventGraph, ni le C++, ni le contenu des modules). Ordre d'exécution : G1, G2, G3, G4, G6, puis G5
(selon Q2).
### 2.0 Préalables (dans l'ordre, avant tout `create_node`)
- P1. Réponses de l'utilisateur à Q1 (et Q2 pour G5).
- P2. MD5 de `GI.uasset` = `d93785fc…` ; sinon (STE-GI passe avant H3 d'après `STE-TACHES.md:26-28`) refaire les dumps
`LPDumpFunction -BP=GI -Fn=<f> -Pins -Exec` des 4 fonctions et comparer les nœuds cités ici (lp-commandlets).
- P3. Reconfirmer G3 sur un dump 5.8 neuf : RSN date d'avant STB/STC (aucune de ces fonctions n'est citée par CHG-281 à 296,
JM:1136, 1143-1154 ; `STD-SCN-DIAG.md:74-77`) : `.\Tools\RunCommandlet.ps1 -Lot STE-D -Commandlet LPDumpFunction -Args '-BP=GI -Fn=ReturnStatIfNeeded -Pins -Exec'`, à joindre à STE-D2.
- P4. Gel annoncé (lp-mcp § 1.7), `.\Tools\Snapshot.ps1 -Lot STE-H3` (GI.uasset), `AssetTools.load_asset` `/Game/BPs/GI`.
- P5. Pour chaque nœud existant cité : `get_node_infos {nodes:[{refPath:"/Game/BPs/GI.GI:<Graphe>.K2Node_<Nom>"}]}` ;
les liens doivent égaler la colonne « avant » ; tout écart arrête l'IMPL.
### 2.1 Conventions communes
- `type_id` (lp-mcp § 3) : Branch = `Utilitaires|ContrôleDeFlux|Branche` ; IsValid (fonction pure, nœud `K2Node_CallFunction`
comme en CHG-290) = `Utilitaires|IsValid` ; IsValidIndex = `Utilitaires|Tableau|IsValidIndex`.
- Indices de broches : par direction, dans l'ordre des broches du nœud (règle déduite de lp-mcp § 3 : Branch entrées
exécution 0 / Condition 1, sorties then 0 / else 1 ; VariableSet entrées exécution 0 / valeur 1, sortie then 0).
Branch : indices établis par lp-mcp. **Tous les autres** (IsValid : `Object` en entrée, `ReturnValue` en sortie ;
IsValidIndex : `TargetArray`, `Index` en entrée, `ReturnValue` en sortie ; broches des nœuds existants) : le nom est
donné ici, l'indice se **relit par `get_node_infos`** avant `connect_pins` (lp-mcp § 3 « vérifier les autres »), la broche
`self` cachée des fonctions statiques pouvant compter. Valeurs attendues (estimées, ordre des dumps) : IsValid `Object`
entrée 1, `ReturnValue` sortie 0 ; IsValidIndex `TargetArray` entrée 1, `Index` entrée 2, `ReturnValue` sortie 0.
- IsValidIndex : `Index` laissé à `0` (valeur par défaut, à relire) ; `TargetArray` prend le type `Person` au câblage
(relu en CHG-282 : « tableaux typés Person »).
- Cascades de `Branch`, jamais de AND / OR / Select purs (lp-mcp § 1.4 et § 3-4). Une sortie de données peut alimenter
plusieurs entrées : les liens existants restent.
- Après chaque fonction : `compile_blueprint` + `hyper_blueprint_debug_inspect {blueprintAssetPath:"/Game/BPs/GI.GI", graphName:"<Graphe>"}` = 0 diagnostic ;
`save_assets {asset_paths:["/Game/BPs/GI"]}` à la fin.
### 2.2 G1 — `setActorVar` (F1, 32 dans L2) — D `GI__setActorVar.txt`
Avant : `K2Node_FunctionEntry_0.then` → `K2Node_IfThenElse_2.execute` (l. 121-122) ; `FunctionEntry_0.Person` →
`VariableGet_0.self`, `VariableGet_1.self`, `VariableGet_8.self` (l. 123) ; `IfThenElse_2.Condition` ← `BooleanOR`
(`CommutativeAssociativeBinaryOperator_1`, l. 253-257) de `SaveSlot > 0` (`CallFunction_5`, l. 243-247) et `isPlayer`
(`VariableGet_8`, l. 258-260) ; `IfThenElse_2.else` (vide) (l. 252).
Après : `create_node` IsValid `G1-V`, Branch `G1-B` ; `break_pins` (`FunctionEntry_0.then`, `IfThenElse_2.execute`) ;
`connect_pins` : `FunctionEntry_0.then` → `G1-B.execute` (0) ; `FunctionEntry_0.Person` → `G1-V.Object` ;
`G1-V.ReturnValue` → `G1-B.Condition` (1) ; `G1-B.then` (0) → `IfThenElse_2.execute`. `G1-B.else` (1) : rien.
Comportement : personne nulle → aujourd'hui `SaveSlot` lu 0 et `isPlayer` lu faux (valeur effacée,
UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3242-3244`), OR faux, `else` vide : rien n'est écrit. Après : rien
n'est écrit. **Identique** (établi), sans les deux lectures.
### 2.3 G2 — `getActorVar` (F1, 142 dans L2) — D `GI__getActorVar.txt`
Écart au DIAG (`STD-SCN-DIAG.md:161`) : `getActorVar` ne lit pas `Variable.Person[0]` ; `Person` est son paramètre
d'entrée (l. 121-124). La garde est donc `IsValid(Person)` seul. Même constat pour `setActorVar` (G1).
Avant : `FunctionEntry_0.then` → `VariableSet_0.execute` (l. 122) ; `Person` → `VariableGet_0.self` (`Prefix`),
`VariableGet_1.self` (`SaveSlot`) (l. 123) ; `FunctionResult_1.execute` ← `MacroInstance_1.Completed`, `Float = '0.0'` (l. 125-127).
Après : IsValid `G2-V`, Branch `G2-B` ; `break_pins` (`FunctionEntry_0.then`, `VariableSet_0.execute`) ;
`connect_pins` : `FunctionEntry_0.then` → `G2-B.execute` ; `FunctionEntry_0.Person` → `G2-V.Object` ;
`G2-V.ReturnValue` → `G2-B.Condition` ; `G2-B.then` → `VariableSet_0.execute` ; `G2-B.else` → `FunctionResult_1.execute`.
Comportement : personne nulle → aujourd'hui clé = `"" + "0"` = `"0"` (`Concat_0`, l. 138-142), `Map_Find(ActorVars, "0")`
(l. 143-148) ; clé absente → tableau vide → `Completed` → `FunctionResult_1` → 0.0. Après : 0.0 directement.
**Identique si `ActorVars` n'a pas de clé `"0"`** (R1).
### 2.4 G3 — `ReturnStatIfNeeded` (F1, 93 dans L2 ; F2 si tableau vide) — RSN (à reconfirmer, P3)
Avant : `K2Node_CallFunction_81822` (`GetVariable`, Name ← `Split.LeftS`) `.then` → `VariableSet_1019.execute` (RSN:140-145) ;
`BreakStruct_2805.Person_24_…` → `GetArrayItem_10794.Array` (RSN:146-155), index 0, `Output` → `VariableSet_1019.Person`
(RSN:156-159) ; `VariableSet_1019.then` → `CallFunction_7798.execute`, `Output_Get` → `7798.self` (RSN:39-43) ;
`7798` = `Person_C::QueryStat`, `then` → `FunctionResult_7`, `Value` → `FunctionResult_7.Float` (RSN:160-171).
Les nœuds cités par le DIAG (`_81822` → `_7798`) sont trouvés (établi).
Après : IsValidIndex `G3-I`, Branch `G3-B1`, IsValid `G3-V`, Branch `G3-B2` ; `break_pins` (`81822.then`,
`VariableSet_1019.execute`) et (`VariableSet_1019.then`, `7798.execute`) ; `connect_pins` (10) :
`81822.then` → `G3-B1.execute` ; `BreakStruct_2805.Person_24_4B712C904750AD4CE1580DBEBD9E7396` → `G3-I.TargetArray` ;
`G3-I.ReturnValue` → `G3-B1.Condition` ; `G3-B1.then` → `VariableSet_1019.execute` ; `G3-B1.else` → `FunctionResult_7.execute` ;
`VariableSet_1019.then` → `G3-B2.execute` ; `VariableSet_1019.Output_Get` → `G3-V.Object` ; `G3-V.ReturnValue` →
`G3-B2.Condition` ; `G3-B2.then` → `7798.execute` ; `G3-B2.else` → `FunctionResult_7.execute`.
Comportement : aujourd'hui `QueryStat` sur nul est sauté (ScriptCore.cpp:3234-3237) et `FunctionResult_7` rend la
sortie `Value` jamais écrite dans ce cadre, mise à zéro à l'appel (ScriptCore.cpp:987-993) : 0.0 (`STD-SCN-DIAG.md:161`).
Après : même `FunctionResult_7`, `7798` non exécuté dans ce cadre : 0.0. **Identique** (estimé fort : lecture du VM).
Pas de `FunctionResult` neuf (aucun `type_id` connu en lp-mcp § 3).
### 2.5 G4 — `CallFunction`, commande `setCurrentLocation` (F3 `_32`) — D `GI__CallFunction.txt`
Avant : cas `SwitchString_22.setCurrentLocation` → `CallFunction_184` (`GetVariable`, l. 5452-5457) ; `BreakStruct_52.Person_24_…`
→ `GetArrayItem_73` (l. 5460-5473), `Output` → `CallFunction_185.self` ; `CallFunction_186` (`GetVariable(Parameters[0])`)
`.then` → `CallFunction_185.execute` (l. 5479-5484) ; `185` = `Person_C::setCurrentLocation`, `then` (vide) (l. 5474-5478).
Après : IsValidIndex `G4-I`, Branch `G4-B1`, IsValid `G4-V`, Branch `G4-B2` ; `break_pins` (`186.then`, `185.execute`) ;
`connect_pins` (7) : `186.then` → `G4-B1.execute` ; `BreakStruct_52.Person_24_…` → `G4-I.TargetArray` ; `G4-I.ReturnValue`
→ `G4-B1.Condition` ; `G4-B1.then` → `G4-B2.execute` ; `GetArrayItem_73.Output` → `G4-V.Object` ; `G4-V.ReturnValue` →
`G4-B2.Condition` ; `G4-B2.then` → `185.execute`. Les deux `else` : rien.
Comportement : aujourd'hui `186` s'exécute, `185` est sauté, fin. Après : idem. **Identique** (établi).
### 2.6 G5 — `CallFunction`, commande `setUntil` (F3 `_31`) — **soumise à Q2**
Avant : `CallFunction_187` (`GetVariable`, cas `setUntil`, l. 5501-5506) ; `BreakStruct_54.Person_24_…` → `GetArrayItem_75`
(l. 5509-5522), `Output` → `VariableGet_139.self` (`Prefix`) et `VariableGet_140.self` (`SaveSlot`) (l. 5523-5528) →
`Concat_5` (l. 5529-5533) → `CallFunction_190.Key` (`Map_Add(AI_Untils)`, l. 5567-5573) et `Concat_1.D` → `PrintString`
`CallFunction_193` « (test0104) for … hours by <clé> » (l. 5586-5603) ; `CallFunction_189` (`GetVariable(Parameters[0])`)
`.then` → `190.execute` (l. 5538-5543) ; `190.then` → `193.execute`.
Après : IsValidIndex `G5-I`, Branch `G5-B1`, IsValid `G5-V`, Branch `G5-B2` ; `break_pins` (`189.then`, `190.execute`) ;
`connect_pins` (7) : `189.then` → `G5-B1.execute` ; `BreakStruct_54.Person_24_…` → `G5-I.TargetArray` ; `G5-I.ReturnValue`
→ `G5-B1.Condition` ; `G5-B1.then` → `G5-B2.execute` ; `GetArrayItem_75.Output` → `G5-V.Object` ; `G5-V.ReturnValue` →
`G5-B2.Condition` ; `G5-B2.then` → `190.execute`. Les deux `else` : rien.
Comportement : **modifié**. Aujourd'hui, personne nulle : `Map_Add(AI_Untils, "0", Until)` puis la ligne « by 0 ». Après :
ni l'un ni l'autre. Le chemin d'origine ne peut pas être gardé : le `PrintString` relit `Prefix` / `SaveSlot` du nul (Q2, R2).
### 2.7 G6 — `CallFunction`, commande `getBuilding` (F3 `_33`)
Avant : cas `SwitchString_22.getBuilding` → `CallFunction_178` (`GetVariable`, l. 312, 5339-5344) ; `BreakStruct_48.Person_24_…`
→ `GetArrayItem_71` (l. 5347-5360), `Output` → `CallFunction_179.self` ; `178.then` → `179.execute` ; `179` =
`Person_C::getBuilding`, `then` → `CallFunction_180.execute`, `Building` → `180.Building` (l. 5361-5366) ; `180` =
`AddVariable(VariableName, Class 'Building')` (l. 5373-5384).
Après : IsValidIndex `G6-I`, Branch `G6-B1`, IsValid `G6-V`, Branch `G6-B2` ; `break_pins` (`178.then`, `179.execute`) ;
`connect_pins` (9) : `178.then` → `G6-B1.execute` ; `BreakStruct_48.Person_24_…` → `G6-I.TargetArray` ; `G6-I.ReturnValue`
→ `G6-B1.Condition` ; `G6-B1.then` → `G6-B2.execute` ; `GetArrayItem_71.Output` → `G6-V.Object` ; `G6-V.ReturnValue` →
`G6-B2.Condition` ; `G6-B2.then` → `179.execute` ; `G6-B1.else` → `180.execute` ; `G6-B2.else` → `180.execute`.
Comportement : aujourd'hui `179` est sauté et `180` définit la variable avec `Building` nul (sortie jamais écrite, cadre
mis à zéro, ScriptCore.cpp:987-993). Après : `180` reçoit la même sortie non écrite. **Identique** (estimé fort).
### 2.8 Correspondance nom compilé → nœud (F3)
Le suffixe `_N` de `CallFunc_Array_Get_Item_N` ne se lit pas dans un dump : il vient de l'ordre de compilation (§ 6). La
correspondance est établie par empreinte dans L2 :
- les 24 « (test0104) for … hours by 0 » viennent de `setUntil` (`CallFunction` l. 5599-5603 : `by` + `Prefix` + `SaveSlot`,
soit `"" + "0"` pour un nul ; les PNJ valides impriment `by 1` à `by 4`, 7 lignes `hours by [^0]` dans L2) ;
- `_31` = 96 = 4 × 24 (`setUntil` évalue deux fois le getter, pour `Map_Add` puis `PrintString`, et lit deux propriétés :
L2:20353-20387) ; `_32` = 24 = 1 × 24 (`setCurrentLocation`, un appel par routine) ; `_33` = 33 ≥ 24 (`getBuilding`, placé
avant les branches des routines, deux fois dans `meal.lpai`) ;
- ordre dans L2:20255-20411 : 3 × `getActorVar`, `_33`, `_32`, `_31` × 4, « by 0 », `setActorVar`, soit la suite de
`D:\Game\LifePlay-master 5.8\Content\Modules\vin_Base\AI\entertainment.lpai:7-20`.
Statut : estimé fort (comptes rejouables) ; la VAL le confirme (comptes à 0, § 7).
## 3. Repli
- Avant écriture : instantané `.\Tools\Snapshot.ps1 -Lot STE-H3` (P4).
- Échec de compilation, diagnostic ou lien relu différent : arrêt, `.\Tools\Restore.ps1 -Lot STE-H3` éditeur fermé (GI entier,
pas de retour partiel), puis contrôle MD5 = `d93785fc…`.
- Après commit (gardien) : `git revert` du commit du ticket STE-H3.
- G5 se retire seule (refus à Q2) : G4 et G6 restent valables. G1 à G3 sont indépendantes de G4 à G6.
## 4. Risques R#, questions Q#
Écart É1 (arrêt partiel, fiche § 9) : `CallFunc_Array_Get_Item_41` (14 dans L2) est en tête de pile
`GI_C:CallFunctionReturnBool` (L2:21377-21397 ; Grep multiligne : 153 lignes F3 sous `CallFunction:` et 14 sous
`CallFunctionReturnBool:`), sous `PlayScene` → `CheckConditionsOther` → `CheckEachCondition`. Le DIAG la range dans
`CallFunction` (`STD-SCN-DIAG.md:163`). Aucun dump de `CallFunctionReturnBool` : garde non spécifiée. Commande proposée
(à joindre à STE-D2) : `.\Tools\RunCommandlet.ps1 -Lot STE-D -Commandlet LPDumpFunction -Args '-BP=GI -Fn=CallFunctionReturnBool -Pins -Exec'`.
Empreinte relevée : 1 F2 puis 2 « Accessed None » par lecture (même forme que `setUntil`).
**Q1 (utilisateur) — « acteur vide = état normal ? » (fiche, point (a))** : non tranchable par les seuls dumps. Établi :
- Cas A, conditions des `.lpscene` (`CheckConditionsOther`, ex. `meet_new_person.lpscene:5`
`isWithCompanion() && Random(0, 200) < CurrentCompanion:interpersonal`, `STD-SCN-DIAG.md:33-37`) : l'absence est
prévue par le contenu, qui la teste ; le calcul évalue tous les termes. **État normal** : concerne G3.
- Cas B, routines d'IA `.lpai` lancées par `PlayScene` (24 démarrages « by 0 », tous entre 21:32:09 et 21:32:21, L2:20397
à 29995) : la variable `AI` a un tableau `Person` **de longueur 0**, donc elle est absente (L2:20327). Or le moteur
ajoute toujours `AI` = `[AIDummy]` (longueur 1) avant les routines (EGB:25006-25037 `SpawnDummy` → `AIDummy` →
`AddVariable('AI')` `CallFunction_779` ; EGB:25149-25171 `AddVariable('AI')` `CallFunction_739`). Le contenu des 17
`.lpai` suppose `AI` présent (Grep `\.setUntil\(` sous `Content\Modules` : 23 fichiers, dont les 17 `.lpai` de
`vin_Base\AI`). Estimé : **pas un état normal du contenu**, mais un défaut d'enchaînement du moteur (variable `AI`
retirée ou jamais posée pendant la rafale ; cause non identifiée). Concerne G1, G2, G4, G5, G6 et une partie de G3.
- Question : (A) poser maintenant toutes les gardes (résultat identique sauf Q2 et R1 ; le défaut du cas B est masqué et
instruit à part, hypothèse H5 ci-dessous) ; ou (B) ne poser que G3 (cas A) et instruire H5 d'abord.
H5 proposée (N3, lp-cart) : pourquoi `AI` est absente pendant la rafale 21:32:09-21 (dumps STE-D2 de l'EventGraph :
boucle des routines `CheckingAiIndex`, macro `Async` `ItersPerTck = 7` EGB:24915-24918, effacement des variables de scène).
**Q2 (utilisateur)** : accepter que G5 supprime l'écriture `AI_Untils["0"]` et la ligne « (test0104) … by 0 » quand `AI` est
vide ? Sinon G5 est retirée et `_31` (96) reste.
**Q3 (utilisateur)** : étendre plus tard aux lectures F2 restantes (sites d'appel `getActorVar` `CallFunction_141`,
`setActorVar` `CallFunction_150`, `modifyActorVar` `CallFunction_144` qui passent `Person[0]` en paramètre ; `_30`, 4 lignes,
L2:12087-22406 ; `_41`) ? Chaque cas demande un second appel avec personne nulle sur le chemin « non valide ».
Choix C++ / Blueprint (fiche, point (b)) : **gardes Blueprint `IsValidIndex` + `IsValid`**. Motifs : `Person_C` est une
classe Blueprint, une fonction C++ `GetPersonAtOrNone` ne peut la rendre typée que par un thunk générique (comme
`Array_Get`) ou rendre `AActor*` / `UObject*` et ajouter un `Cast` par site (compatibilité de type des tableaux en
Blueprint : non vérifiée) ; il faut de toute façon un `IsValid` + `Branch` avant chaque appel (le nul reste un nul) ;
coût d'un build C++, d'un test et d'un ticket de plus. CHG-283 a pu le faire car `FString` est natif. Les gardes
Blueprint suivent CHG-282 / CHG-291 (JM:1153, 1144) : 4 nœuds par site, sans build.
- **R1** : clé `"0"` = joueur ? `getActorVar` sur nul lit aujourd'hui `ActorVars["0"]`. Si le joueur a `Prefix` vide et
`SaveSlot` 0 (estimé : `setActorVar` admet `isPlayer` en plus de `SaveSlot > 0`, D `GI__setActorVar.txt:243-257` ; des
PNJ ont `Prefix` vide, EGB:25082-25093), la valeur rendue aujourd'hui est celle du joueur, et G2 rend 0.0. Essai qui
tranche : `LPDumpSave -File=<sauvegarde de la partie STB-JEU2> -Out=<txt>` (lp-commandlets) et recherche d'une clé `"0"`
dans `ActorVars` et `AI_Untils`.
- **R2** : G5 change l'état (`AI_Untils["0"]`) et retire le dénominateur « by 0 » du DIAG ; normaliser par `CHECKING:`.
- **R3** : correspondance `_31` / `_32` / `_33` estimée (§ 2.8) ; la 2e ligne F2 avant chaque `_33` (L2:20327) n'est pas
expliquée (incertain). Si un compte F3 de `CallFunction` reste > 0 en VAL : rapport, pas de correctif improvisé.
- **R4** : STE-GI modifie GI avant H3 : noms de nœuds à recontrôler (P2, P5).
- **R5** : G3 repose sur un dump antérieur à STB / STC (P3).
- **R6** : évaluation des nœuds purs : `GetArrayItem` est évalué pour `IsValid` puis pour l'appel ; sans avertissement car
l'index est valide à ce moment-là (UE `Runtime\Engine\Private\KismetArrayLibrary.cpp:466-468`).
- **R7** : F2 ne tombe pas à 0 : restent les sites de Q3, `_41`, `_30` et les autres lecteurs de `Person[0]` (96 lignes
`Person_24 ->` dans `CallFunction`, Grep `^LogLPDump: Warning:.*Person_24_4B712C904750AD4CE1580DBEBD9E7396 ->`).
- **R8** : masquage (Q1 cas B) : les gardes cachent le symptôme d'un défaut d'enchaînement de l'IA.
Nœuds de l'EventGraph cités d'après EGB (antérieur à STC), **à reconfirmer sur `dumps\GI__EventGraph.txt` (STE-D2)** :
`K2Node_CustomEvent_404` `PlayScene` (EGB:6002-6005) ; `K2Node_CommutativeAssociativeBinaryOperator_74` et
`K2Node_CallFunction_778` (`PrintString` « (test0104) », `execute` vide, donc le fichier joué n'est jamais écrit au
journal, EGB:25133-25148) ; `K2Node_CallFunction_768` `SpawnDummy` (EGB:25006-25010) ; `K2Node_VariableSet_258`
`AIDummy` (EGB:25011-25016) ; `K2Node_CallFunction_779` `AddVariable('AI')` et `K2Node_MakeArray_14` (EGB:25017-25037) ;
`K2Node_CallFunction_739` `AddVariable('AI')`, `K2Node_MakeArray_3`, `K2Node_VariableGet_525` (EGB:25149-25171) ;
`K2Node_VariableSet_259` `CheckingPkgIndex` (EGB:24862-24866) ; `K2Node_CallFunction_745` `getAllSlots` (EGB:24911-24913) ;
`K2Node_MacroInstance_49` `Async` (EGB:24915-24918) ; `K2Node_CallFunction_5384` `GetAllFilesFromFolder(Ext 'lpai')`
(EGB:24638-24643) ; `K2Node_CustomEvent_72` `ContinueAiAfterLoad` (EGB:24623-24625). Aucun n'est modifié par cette SPEC.
## 5. Interdits
- Aucune modification de l'EventGraph, de `CallFunctionReturnBool` (É1), des sites d'appel de Q3, du C++.
- 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 : UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3213-3216` (« Accessed None trying to read
(real) property X in not an UClass », X = variable qui tient le nul) ; contexte nul : expression sautée et valeur de
retour effacée, `:3234-3244`.
- Cadre d'une fonction script non persistant mis à zéro : `ScriptCore.cpp:987-993`.
- Lecture hors bornes : message F2 puis élément remis à sa valeur par défaut (nul) :
UE `Runtime\Engine\Private\KismetArrayLibrary.cpp:466-479`.
- `K2Node_GetArrayItem` non « par référence » devient un appel `Array_Get`, d'où le nom compilé `CallFunc_Array_Get_Item` :
UE `Editor\BlueprintGraph\Private\K2Node_GetArrayItem.cpp:335-349`.
- Suffixe `_N` = n-ième doublon du nom de base : UE `Editor\KismetCompiler\Public\KismetCompilerMisc.h:287-302` ; ordre
d'enregistrement = liste d'exécution linéaire : `Editor\KismetCompiler\Private\KismetCompiler.cpp:3918-3927`, construite
par `Editor\KismetCompiler\Private\EdGraphCompiler.cpp:244-249` (`RemoveAtSwap`) : le suffixe ne se déduit pas d'un dump.
## 7. Critères de VAL (commande → attendu)
1. Dumps après IMPL, `.\Tools\RunCommandlet.ps1 -Lot STE-H3 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=<f> -Pins -Exec'` :
nœuds `setActorVar` 32 → 34, `getActorVar` 15 → 17, `ReturnStatIfNeeded` 95 → 99, `CallFunction` 1 538 → 1 550
(1 546 sans G5) ; liens du § 2 relus ; `DiffRemplacementNoeuds.py` sans autre écart.
2. `hyper_blueprint_debug_inspect` 0 diagnostic par graphe ; `CompileAllBlueprints` éditeur fermé : 0 erreur (lp-validate).
3. Journal de jeu (build Development, parcours de STB-JEU2 : nouvelle partie, ≥ 20 actions seul, ≥ 3 min en ville,
`STD-SCN-DIAG.md:198-206`), Grep en mode `count` :
- F1 ciblée multiligne (§ 1) → **0** (L2 : 267) ;
- `property CallFunc_Array_Get_Item_3[123] ` → **0** (L2 : 153) ; sans G5 : `_3[23]` → 0, `_31` inchangé ;
- `property CallFunc_Array_Get_Item_41 ` → information seulement (L2 : 14) ;
- `property Person in` → seulement des lignes hors des 3 fonctions (L2 : 270 dont 267 ciblées) ;
- `Person_24_4B712C904750AD4CE1580DBEBD9E7396' of length 0` / `CHECKING:` → inférieur à L2 (351 / 2 318 = 0,151) ;
- `hours by 0` → 0 si G5 (L2 : 24) ; `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. R1 / Q2 : `LPDumpSave` avant / après sur la même sauvegarde : clés `"0"` de `ActorVars` et `AI_Untils` relevées.
Fichiers à commiter (dépôt jeu, branche `ue5.8`, ticket au gardien) :
- A `Docs/Migration/etat/ST/STE/STE-H3-SPEC.md`
Tours : 34 / 60
## Constats
- Commandes de comptage rejouées (Grep, mode `count`, sur L2) : `property CallFunc_Array_Get_Item_31 ` → 96 ;
`property CallFunc_Array_Get_Item_32 ` → 24 ; multiligne F3 sous `CallFunction:` → 153, sous `CallFunctionReturnBool:`
→ 14 (somme 167 = `STD-SCN-DIAG.md:26`) ; F1 multiligne des 3 fonctions → 267 ; `hours by 0` → 24 lignes ;
`hours by [^0]` → 7 lignes.
- Dans `CallFunction`, 96 lignes `Person_24 ->` (Grep cité en R7), dont 35 nœuds `GetArrayItem` dont la sortie va à un
`self` (Grep multiligne `Array +-> K2Node_\w+\.Person_24_\w+\r?\n[^\n]*Dimension 1[^\n]*\r?\n[^\n]*Output +-> [^\n]*\.self`,
35 blocs) : seuls les 3 de G4 à G6 sont observés en défaut dans L2.
- Grep d'un dossier sous `etat\` : les `.txt` y sont ignorés par git ; passer `glob` (ex. `*__*.txt`) ou le fichier en
`path`, sinon 0 résultat trompeur (essai de cette fiche).
- Journal et gabarits lus 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`.