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

482 lines
45 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-H5b — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-opus-5-5 — Agent lp-cart — Skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths (préchargés), lp-mcp (lu, § 1-4), lp-doc (gabarits)
# STE-H5b-SPEC : reposer la variable de scène `AI` avant chaque routine d'IA (correctif F-a) (2026-10-10)
Lecture seule stricte (aucun éditeur, commandlet, build ni écriture hors de ce fichier). Gabarit SPEC :
`D:\Game\LifePlay-master 5.8\.claude\skills\lp-doc\gabarits.md:36-38`. Décision de l'utilisateur : Q-H5-1 = « Corriger (F-a) »
(`STE-TACHES.md:23`). **Statut : complet, sous un préalable** : le dump 5.8 d'`AddVariable` avec broches d'exécution
(P3, commande au § 2.0) n'existe pas. Le reste de la SPEC est exécutable ; P3 contrôle 3 points (A1-A3) avant l'IMPL.
Abréviations (chemins absolus) :
- **EG** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\GI__EventGraph_apres-GARDES.txt` (`-Fn=EventGraph -Pins -Exec`,
17:43:39, après les gardes, EG:1-4 ; 6233 nœuds, EG:120) ;
- **L6f** = `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\portage55\L6f\L6f_dump_GI.txt` (GI **5.5** du 2026-09-30, `-Fn=* -Pins`,
**sans** broches d'exécution : le flux de données seulement, `L6f_LPDumpFunction_20260930-102202.txt:1`) ;
- **L2** = `D:\Game\LifePlay_Build_5.8_STB-JEU2_Development\Windows\LifePlay\Saved\Logs\LifePlay.log` (avant les gardes) ;
- **L3** = `D:\Game\LifePlay_Build_5.8_STF-STE-LISIB_Development\Windows\LifePlay\Saved\Logs\LifePlay.log` (J3, état actuel B8B97C46) ;
- **H5D** = `STE-H5-DIAG.md`, **H3S** = `STE-H3-SPEC.md`, **H3bS** = `STE-H3b-SPEC.md`, **GIMPL** = `STE-GARDES-IMPL.md`
(dossier `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\`) ;
- **UE** = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\` (moteur installé, lu).
État du Blueprint (rejoué) : `md5sum "/d/Game/LifePlay-master 5.8/Content/BPs/GI.uasset"` → `b8b97c46b825a6af942baabd5bf74c56` ;
`git status --short -- Content/BPs/GI.uasset` → vide ; dernier commit du fichier `5c55db4` (STE-R5). EG a été pris après
l'IMPL des gardes (GIMPL § 1 : B8B97C46 à 17:33), donc **EG décrit le GI actuel** (établi).
## 1. Objectif mesurable
Que chaque `PlayScene` d'un fichier `.lpai` lancé par la boucle d'IA (`MacroInstance_45`) soit précédé, **dans la même
exécution synchrone**, de `AddVariable('AI', [AIDummy])`. Un vidage de `Variables` par `UpdateCheckForScenes`
(`CallArrayFunction_5412`, EG:6616-6623) ne peut se produire qu'à une reprise latente (`Delay(0)` de la macro `Async`,
§ 6) ; il ne peut donc plus tomber entre la pose de `AI` et la routine qui la lit.
Mesures :
- statique : EG après IMPL = 6234 nœuds (+1), liens du § 2.4 relus, compilation sans diagnostic (§ 7, V1-V3) ;
- en jeu (J4, même parcours que J3 plus une rafale) : `hours by 0` = 0 ; routines valides en hausse par rapport à J3
(ratios du § 7, V5) ; aucune famille d'avertissements nouvelle.
Volume : **1 nœud créé, 1 `break_pins`, 6 `connect_pins`, 2 `set_pin_value` (+1 relecture), 0 suppression**, un seul
graphe (`EventGraph`).
Ce que l'utilisateur verra (effet accepté par Q-H5-1) :
- en temps accéléré ou après un déplacement (rafale de routines), la routine d'un PNJ (repas, travail, sortie…)
s'applique à ce PNJ : il change de lieu (`setCurrentLocation`) et reçoit sa prochaine échéance (`AI_Untils[slot]`) ;
aujourd'hui, une partie de ces routines tourne « à vide » (sans personne, effets masqués par les gardes G4-G10) et le
PNJ est repris à la seconde suivante ;
- moins de routines jouées par PNJ : la boucle s'arrête au premier `.lpai` qui pose une échéance future (condition C de
`BooleanAND_65`, EG:24880-24887, 24792-24796) ;
- les compteurs du PNJ (`ActorVars`, ex. `entertainment_today`, `entertainment.lpai:19-20`) avancent au bon moment, ce qui
évite des répétitions ; on croise plus souvent un PNJ au lieu où sa routine l'envoie ;
- journal : plus de lignes `(test0104) for … hours by <n>` et `[AI] <n> moves to …` pendant les rafales ;
- rien d'autre : scènes du joueur, interface, PNJ hors routine inchangés (§ 4, R5).
## 2. Fichiers touchés, ordre
Un seul fichier : `Content\BPs\GI.uasset`, graphe `EventGraph` (événement `UpdateAI`, corps de `MacroInstance_45`). Ni
`UpdateCheckForScenes`, ni `CallFunction_739` / `_779`, ni les fonctions `AddVariable` / `CallFunction`, ni les gardes
G1-G11, ni le C++, ni le contenu des modules.
### 2.0 Préalables (dans l'ordre, avant tout `create_node`)
- **P1.** Décision Q-H5-1 = F-a : acquise (`STE-TACHES.md:23`). Q-H5b-1 (preuve, § 4) : à demander avant J4, pas avant l'IMPL.
- **P2.** MD5 de `GI.uasset` = `b8b97c46…` et `git status` vide sur ce fichier. Sinon : nouveau dump
`.\Tools\RunCommandlet.ps1 -Lot STE-H5b -Commandlet LPDumpFunction -Args '-BP=GI -Fn=EventGraph -Pins -Exec'` et
comparaison des nœuds du tableau 2.2 ; tout écart arrête l'IMPL.
- **P3 (à compléter, bloquant)** — dump 5.8 d'`AddVariable` avec flux d'exécution, éditeur fermé, BUILD-GO de Big Boss
(une session à la fois sur la copie UE, `STE-TACHES.md:41-45`) :
`.\Tools\RunCommandlet.ps1 -Lot STE-H5b -Commandlet LPDumpFunction -Args '-BP=GI -Fn=AddVariable -Pins -Exec'`
(≈ 1 min, comme EG:3). Le flux de données 5.5 est lu dans L6f:10070-10273 ; le dump 5.8 doit confirmer :
- **A1** remplacement par nom : boucle sur `Variables` (`MacroInstance_2707`, L6f:10098-10101), égalité **sans casse**
du nom (`EqualEqual_StriStri` `CallFunction_34376`, L6f:10112-10118) → `Array_Set` à l'indice trouvé
(`CallArrayFunction_4346`, L6f:10119-10124) et `Replaced = true` (L6f:10139-10145) ; sinon `Array_Add` à la fin
(`CallArrayFunction_3373`, L6f:10083-10087). Attendu : jamais deux entrées `AI` ;
- **A2** la branche `destroyDummy` (`CallFunction_0`, L6f:10212-10215) n'est atteinte que pour l'entrée remplacée, si
elle est valide (`MacroInstance_1960` IsValid, L6f:10168-10169), si `SaveSlot == 0` (`IfThenElse_4367`,
L6f:10170-10179) et si le OU `BooleanOR_236` est faux (ancienne = joueur, non cachée, **ancienne = nouvelle**
`CallFunction_1`, a des `SavedClothes` ; L6f:10188-10224, 10262-10273) ;
- **A3** le `SwitchString_75` sur `Name` (L6f:10149-10151, cas `OptionChosen…` → `VariableSet_14216`) laisse `AI` sur le
chemin par défaut (même chemin que `CallFunction_739` aujourd'hui).
Un point non confirmé → arrêt, rapport à l'orchestrateur, pas d'IMPL. Facultatif (non bloquant) :
`... -Args '-BP=GI -Fn=Async -Pins -Exec'` (la macro est un graphe de `GetAllGraphs`, donc trouvée par `-Fn`, § 6).
- **P4.** Gel annoncé à toutes les sessions (lp-mcp § 1.7) ; instantané
`.\Tools\Snapshot.ps1 -Lot STE-H5b -Path Content\BPs\GI.uasset` (paramètres `-Lot`, `-Path` obligatoires,
`Tools\Snapshot.ps1:17-22`) ; tag d'entrée `STE-H5b-avant` par le gardien si l'orchestrateur le souhaite ; éditeur
headless (lp-mcp § 2) ; `AssetTools.load_asset` `/Game/BPs/GI`.
- **P5.** `get_node_infos {nodes:[{refPath:"/Game/BPs/GI.GI:EventGraph.K2Node_<Nom>"}]}` pour chaque nœud du tableau 2.2 :
les liens doivent égaler la colonne « avant » ; relever au passage la position (`pos`) de `IfThenElse_148` et de
`CallFunction_743`, et les indices de broches.
- **P6.** `find_node_types {graph:{refPath:"/Game/BPs/GI.GI:EventGraph"}, type_id_filter:"AddVariable", context_pins:[]}` :
attendu `Scène|AddVariable` (relu en STE-H3b pour `CallFunction_360`, GIMPL:33, 39). Autre résultat → arrêt.
### 2.0 bis P3 contrôlé sur le dump 5.8 — complément A-CART pour l'orchestrateur LOT STE (2026-10-10, après STE-D5)
Auteur : agent lp-cart (A-CART), claude-opus-5-5, skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths ; lecture
seule, seul ce fichier et `STE-H3c-SPEC.md` écrits. Dumps (sous `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\`) :
**AV** = `GI__AddVariable_D5.txt` (`-BP=GI -Fn=AddVariable -Pins -Exec`, lot STE-D5, 20:51:17, code 0,
`=== GI :: AddVariable (49 noeuds) ===` AV:120 ; le bloc AV:367-416 est l'écho tronqué du même dump) ; **AS** =
`GI__Async_D5.txt` (`-Fn=Async`, 20:52:03, code 0, 20 nœuds, AS:120 ; écho AS:210-259). `GI.uasset` : dernière écriture
17:33:20 (`stat -c '%y %s'` → `2026-10-10 17:33:20 … 52540467`), MD5 `b8b97c46…`, `git status` vide : AV et AS décrivent
l'état B8B97C46 (établi). Tous les nœuds cités par P3 existent dans AV sous le même nom que dans L6f.
**P3 est satisfait : A1, A2 et A3 sont confirmés ; la SPEC est exécutable** (P1, P2, P4-P6 restent à faire à l'IMPL).
- **A1 confirmé** (établi, sauf la sortie de `Break`, estimée forte). Entrée `FunctionEntry_3606.then` → `SwitchString_75`
(AV:122) ; `Default` → `MacroInstance_2707` (`ForEachLoopWithBreak` sur `Variables`, AV:151-158, 219). `LoopBody` →
`IfThenElse_3951` (AV:174-178), condition `EqualEqual_StriStri` (`CallFunction_34376`, AV:169-173 : `A` ← paramètre
`Name` par `VariableGet_19198`, `B` ← `BreakStruct_1527.Name_2_…`) : comparaison sans casse ; `else` vide → élément
suivant. `then` → `MacroInstance_1960` (IsValid), dont toutes les issues finissent sur `Array_Set`
(`CallArrayFunction_4346`, AV:179-186 : `Index` ← `MacroInstance_2707.Array Index`, `Item` ← `MakeStruct_614`) →
`VariableSet_3566` `Replaced = true` (AV:201-205) → `Knot_488` → `MacroInstance_2707.Break` (AV:154, 215-217).
`Completed` → `IfThenElse_4283` sur `Replaced` (AV:206-212) : `then` vide, `else` → `Array_Add` (`CallArrayFunction_3373`,
AV:134-140). `Replaced` est une variable locale (ses nœuds n'ont pas de broche `self`, AV:201-205, 211-212, au contraire
des variables de GI, AV:133, 229 ; estimé fort), remise à sa valeur par défaut à chaque appel (H3bS:100-102). Conclusion :
la première entrée de même nom est remplacée sur place et la boucle s'arrête ; `Array_Add` seulement si aucune entrée ne
porte ce nom. `AddVariable` ne crée jamais une deuxième entrée `AI`. Si `Break` ne menait pas à `Completed` (macro
standard, non dumpée), il n'y aurait pas d'ajout non plus : A1 tient dans les deux cas.
- **A2 confirmé** (établi). `destroyDummy` (`CallFunction_0`, AV:297-302) n'est atteint que par : `IfThenElse_3951.then` →
`MacroInstance_1960` `Is Valid` (ancienne `Person[0]` = `GetArrayItem_2453`, AV:238-246) → `IfThenElse_4367` sur
`SaveSlot == 0` (`CallFunction_27614`, AV:247-259) `then` → `IfThenElse_4819` (AV:278-282) `else`, condition `BooleanOR`
`CommutativeAssociativeBinaryOperator_236` (AV:283-289) de : A ancienne `== PlayerCharacter` (`CallFunction_24845`,
AV:270-274) ; B `Not(ancienne.bHidden)` (`CallFunction_565`, AV:290-296) ; C ancienne `==` nouvelle `Person[0]`
(`CallFunction_1` ← `GetArrayItem_0` du paramètre `Person`, AV:303-311) ; D `Length(ancienne.SavedClothes) > 0`
(`CallFunction_5`, AV:355-366). Suite de la branche : `PrintString` « DestroyDummy test1903 via AddVariable »
(`CallFunction_2`) → `Array_RemoveItem(PersonsUsedThisScene)` (`CallArrayFunction_5493`) → `destroyDummy(Dummy = ancienne,
KeepAlreadyGot = false)` → `PrintString` « (test0304) DestroyDummy was from AddVariable » (`CallFunction_606`,
`bPrintToScreen = false`) → `Array_Set` (AV:263-269, 297-333). Pour H5-A, cas attendu : ancienne `AI` = `[AIDummy]` posée
par `CallFunction_739` juste avant la boucle, même objet que la nouvelle : `SaveSlot != 0` (seule condition d'entrée du
`else` de `IfThenElse_148`, § 2.5) ferme la branche dès `IfThenElse_4367`, et le terme C la fermerait aussi. Cas d'un autre
objet (si `AIDummy` changeait entre deux images ; écrivain hors GI, R3) : la branche ne s'ouvre que pour une ancienne
personne à `SaveSlot == 0`, cachée, hors joueur, sans `SavedClothes` ; c'est le comportement d'origine d'`AddVariable`,
déjà possible avec `_739` / `_779` (incertain, non observé ; V5 compte `DestroyDummy was from AddVariable`). R2 : levé
pour le cas attendu.
- **A3 confirmé, libellé corrigé** (établi). `SwitchString_75` (AV:218-223) : `Selection` ← paramètre `Name` ; un seul cas,
la broche **`choice`** (et non « `OptionChosen…` » : L6f:10149-10155, sans broches d'exécution, ne montrait pas le nom du
cas ; `OptionChosenIndex` est la variable écrite) → `VariableSet_14216` `OptionChosenIndex = Round(Float[0])`
(`CallFunction_71442`, `GetArrayItem_8856`, AV:224-237), dont `then` est **vide** (AV:226) : pour `Name = choice`, rien
n'est enregistré dans `Variables`. Broche cachée `NotEqual_StriStri` (AV:222) : comparaison sans casse (estimé, nom de la
fonction). `AI` ≠ `choice` → `Default` → A1 (établi). Le `PrintString` `CallFunction_4` (« <Name> Add Variable <objet> »)
a `execute` vide (AV:344-345) : jamais exécuté (cohérent avec 0 ligne ` Add Variable` dans L3, R2).
- Autres constats utiles à l'IMPL (établis sur AV) : aucun nœud latent ni `FunctionResult` parmi les 49 nœuds (types,
AV:121-366) : H5-A rend la main de façon synchrone, puis `H5-A.then` → `CallFunction_743` ; `Float[0]` n'est lu que sur le
cas `choice` (`GetArrayItem_8856`) : le `MakeArray_5` vide de H5-A ne lève pas de F2 ; `Person[0]` de l'ancienne entrée
(`GetArrayItem_2453`) n'est lu qu'après un nom égal : ancienne `AI` de longueur 1, pas de F2 (lecture que G12 garde,
`STE-H3c-SPEC.md` § 2.2 bis).
**§ 2.3 confirmé sans changement** : `type_id` `Scène|AddVariable` (relu en 5.8, GIMPL:39 ; P6 le revérifie) ; broches de
H5-A = paramètres d'`AddVariable` `Name, Class, Array, Bool, Float, String, Person, Building` (AV:121-130), même ordre que
`CallFunction_739` (EG:25149-25160, qui montre aussi `self`, EG:25152 : l'indice exact reste à relire, § 2.1) ; valeurs
`Name = 'AI'`, `Class = 'Person'`, `Array = 'false'` copiées telles quelles dans la structure (`MakeStruct_614` ←
`VariableGet_18510` à `_18516`, `Building` ← entrée, AV:141-150, 187-200 ; `Class` et `Array` ne sont testés nulle part).
Volume inchangé : 1 nœud, 1 `break_pins`, 6 `connect_pins`, 2 `set_pin_value` (+1 relecture) ; EG après = 6234 nœuds.
**Macro `Async` (AS), sémantique relue** (établi sauf mention) :
- `Tunnel_0.execute` → `IfThenElse_0` sur l'entrée `Condition` (`BooleanAND_65` pour `MacroInstance_45`, EG:25423) ;
`else` → `Knot_3` → `Completed` (AS:129-133, 178-180) : **condition fausse = fin de la boucle**, pas de pause.
- `then` → `IfThenElse_2` sur `SkipAsync` (variable de GI, AS:181-188) : vrai → `SkipAsync = false`, `Local = -1`,
`Delay(0)` (AS:189-198) ; faux → `IfThenElse_1` sur `(Local % ItersPerTck == 0) AND (Local >= 0)` (AS:141-155, 200-209).
- `IfThenElse_1.then` → `Knot_1` → `ExecutionSequence_0` : `then_0` → `Loop Body`, puis `then_1` → `Knot_2` (AS:163-172) ;
`IfThenElse_1.else` → `Delay(0)` (`CallFunction_2`, `Duration = 0`, AS:156-162), `then` → `Knot_2`.
- `Knot_2` → `AssignmentStatement_0` `Local = Local + 1` → retour à `IfThenElse_0` (AS:134-138, 173-177). `Local`
(`TemporaryVariable_0`, AS:139-140) n'est pas remis à zéro à l'entrée.
Conséquences :
1. Le corps (`IfThenElse_148` → H5-A → `PlayScene`) s'exécute en entier sur `then_0`, avant `then_1` ; le seul point latent
de la macro est le `Delay` (AS:156) : pas de reprise entre la pose de `AI` et la routine (objectif du § 1, établi).
2. **Correction du § 2.5** (« jusqu'à `len(LPAI)` itérations à 3 par image ») : le corps ne part que si `Local % 3 == 0`
(`ItersPerTck = '3'`, EG:25424) ; les deux valeurs suivantes de `Local` passent chacune par un `Delay(0)`. Le gestionnaire
d'actions latentes ne traite un objet qu'une fois par passe (`bProcessedThisFrame`, UE
`Runtime\Engine\Private\LatentActionManager.cpp:223-231, 250-257`) : environ un `.lpai` toutes les deux images (estimé
fort). Entre deux `.lpai` d'un même PNJ, il y a **toujours** au moins une reprise latente, donc une fenêtre de vidage :
F-a reste nécessaire à chaque itération (rien à changer au § 2.3).
3. Ligne F-b du § 2.6 et « Incertain » des Constats : la sortie d'`Async` quand la condition devient fausse est établie
(`Completed`, fin de boucle) ; F-b arrêterait la boucle d'IA au lieu de la suspendre (verdict « plus invasive » confirmé).
Corrections de renvois : V4 (§ 7) joint `dumps\GI__AddVariable_D5.txt` (et non `GI__AddVariable.txt`) ; R1 et R2 (§ 4) sont
levés par ce complément ; la commande P3 du § 2.0 n'est plus à lancer.
Tours du complément (A-CART, commun au § 2.7 et à `STE-H3c-SPEC.md` § 2.2 bis) : 19 / 50.
### 2.1 Conventions (reprises de H3S § 2.1, H3bS § 2.1 et GIMPL § 2)
- `refPath` d'un nœud : `/Game/BPs/GI.GI:EventGraph.K2Node_<Nom>` (lp-mcp § 3) ; G-H2 a déjà été posée dans ce graphe
(GIMPL:35).
- Indices de broches par direction. Établis : `IfThenElse` sortie `then` 0 / `else` 1 (lp-mcp § 3). **Tous les autres se
relisent par `get_node_infos` avant `connect_pins`.** Attendus (estimés) pour le nouvel `AddVariable` : entrées
`execute` 0, `Name` 1, `Class` 2, `Array` 3, `Bool` 4, `Float` 5, `String` 6, `Person` 7, `Building` 8, sortie `then` 0,
si la broche cachée `self` n'est pas comptée (cas observé : IsValidIndex `TargetArray` = 0, GIMPL:38) ; sinon +1 sur
chaque entrée après `execute`. `CallFunction_743` : entrée `execute` 0. `MakeArray_3` à `_6` : sortie `Array` 0.
- Une sortie de données peut alimenter plusieurs entrées (les liens existants restent) ; une entrée d'exécution peut
recevoir plusieurs liens (H3bS:78-79).
- Après le graphe : `compile_blueprint` avec avertissements en erreurs (`hyper_blueprint_debug_inspect` absent du
serveur, GIMPL:44-46), puis `save_assets {asset_paths:["/Game/BPs/GI"]}`.
### 2.2 Nœuds existants (colonne « avant », EG)
| Nœud | Rôle | Liens avant (EG) |
|---|---|---|
| `K2Node_MacroInstance_45` | macro `Async`, `ItersPerTck = 3` | `Loop Body` → `IfThenElse_148.execute` ; `Completed` → `VariableSet_280` ; `Condition` ← `BooleanAND_65` (EG:25422-25428) |
| `K2Node_IfThenElse_148` | `AIDummy.SaveSlot == 0` | `execute` ← `MacroInstance_45.Loop Body` ; `Condition` ← `CallFunction_811` ; `then` → `VariableSet_292.execute` ; `else` → `CallFunction_743.execute` (EG:25750-25754) |
| `K2Node_CallFunction_811` / `VariableGet_576` | `SaveSlot` de `VariableGet_529.AIDummy` `== 0` | EG:25742-25749 |
| `K2Node_CallFunction_743` | `PlayScene(LPAI[CheckingPkgIndex])` | `execute` ← `MacroInstance_43.LoopBody`, `IfThenElse_148.else` ; `then` → `MacroInstance_47` ; `FilePath` ← `GetArrayItem_51.Output` (EG:24891-24895) |
| `K2Node_MacroInstance_43` | `WhileLoop` mort | `execute` (vide), `LoopBody` → `CallFunction_743.execute` (EG:24975-24979) |
| `K2Node_CallFunction_739` | `AddVariable` existant, modèle | `execute` ← `VariableSet_259.then` ; `then` → `VariableSet_281.execute` ; `Name = 'AI'`, `Class = 'Person'`, `Array = 'false'` ; `Bool` ← `MakeArray_4`, `Float` ← `MakeArray_5`, `String` ← `MakeArray_6`, `Person` ← `MakeArray_3`, `Building` (vide) (EG:25149-25160) |
| `K2Node_MakeArray_4` / `_5` / `_6` | tableaux vides `Bool` / `Float` / `String` | `Array` → `CallFunction_739.Bool` / `.Float` / `.String` (EG:25161-25166) |
| `K2Node_MakeArray_3` | `[AIDummy]` | `Array` → `CallFunction_739.Person` ; `[0]` ← `VariableGet_525.AIDummy` (EG:25167-25169) |
| `K2Node_VariableGet_525` | `AIDummy` | `AIDummy` → `MakeArray_3.[0]` (EG:25170-25172) |
### 2.3 Étapes MCP (une par appel, dans l'ordre ; le nouveau nœud est noté **H5-A**)
1. `create_node {graph:{refPath:"/Game/BPs/GI.GI:EventGraph"}, type_id:"Scène|AddVariable", pos:{x,y}}` ; position
libre près de `IfThenElse_148` (sans effet sur l'exécution).
2. `get_node_infos` de H5-A : appel `GI_C::AddVariable`, mêmes broches que `CallFunction_739` (EG:25149-25160) ; noter
son nom (`K2Node_CallFunction_<n>`) et les indices réels (§ 2.1). Sinon arrêt et repli (§ 3).
3. `break_pins` : `IfThenElse_148.else` (sortie 1) / `CallFunction_743.execute` (entrée 0).
4. `connect_pins` : `IfThenElse_148.else` (1) → `H5-A.execute`.
5. `connect_pins` : `H5-A.then` → `CallFunction_743.execute` (0). Le lien mort `MacroInstance_43.LoopBody` reste.
6. `connect_pins` : `MakeArray_3.Array` → `H5-A.Person`.
7. `connect_pins` : `MakeArray_4.Array` → `H5-A.Bool`.
8. `connect_pins` : `MakeArray_5.Array` → `H5-A.Float`.
9. `connect_pins` : `MakeArray_6.Array` → `H5-A.String`.
10. `set_pin_value` : `H5-A.Name` = `AI` (valeur relue sur `CallFunction_739`, EG:25153).
11. `set_pin_value` : `H5-A.Class` = `Person` (EG:25154).
12. Relire `H5-A.Array` : doit valoir `false` (EG:25155) ; ne le poser que s'il diffère. `Building` : rien.
13. `get_node_infos` de H5-A, `IfThenElse_148`, `CallFunction_743`, `CallFunction_739`, `MakeArray_3` à `_6` : liens = § 2.4.
14. `compile_blueprint` (avertissements en erreurs) : 0 erreur, 0 avertissement ; puis `save_assets`.
15. Fermeture (lp-mcp § 2 : `Stop-Process`, `.\Tools\UeState.ps1` = 0), MD5 après relevé, `git status --short -- Content Config Source`.
Repli documenté de l'étape 6 seulement : si `connect_pins` refuse `MakeArray_3.Array` → `H5-A.Person`, créer un Make Array
(`type_id` `Utilitaires|Array|Faireuntableau`, GIMPL:39), câbler d'abord sa sortie `Array` → `H5-A.Person` (typage,
H3bS:83-84), puis `VariableGet_525.AIDummy` → `[0]` (2 nœuds au lieu de 1, EG 6235). Tout autre refus → arrêt et repli.
### 2.4 Après (liens attendus, relus à l'étape 13 et dans le dump V1)
- `IfThenElse_148.else` → `H5-A.execute` (seul lien) ;
- `H5-A` : `execute` ← `IfThenElse_148.else` ; `then` → `CallFunction_743.execute` ; `Name = 'AI'`, `Class = 'Person'`,
`Array = 'false'` ; `Bool` ← `MakeArray_4.Array`, `Float` ← `MakeArray_5.Array`, `String` ← `MakeArray_6.Array`,
`Person` ← `MakeArray_3.Array`, `Building` (vide) ;
- `CallFunction_743.execute` ← `MacroInstance_43.LoopBody`, `H5-A.then` ;
- `MakeArray_3` / `_4` / `_5` / `_6` : `Array` → `CallFunction_739.<broche>` **et** `H5-A.<broche>` ;
- aucun autre nœud de EG modifié.
### 2.5 Comportement avant / après
- Avant : `AI` posée une fois par PNJ (`CallFunction_739`, EG:25149-25171), puis jusqu'à `len(LPAI)` itérations à 3 par
image (`MacroInstance_45`, EG:25422-25428) ; un vidage entre deux images retire `AI` pour les `.lpai` suivants (H5D § 3-4).
- Après : à chaque itération qui joue un `.lpai` (`SaveSlot != 0`), `AI` est reposée juste avant `PlayScene`. Si `AI`
existe déjà avec le même `AIDummy`, `AddVariable` la remplace par elle-même (A1) ; cette situation existe déjà à chaque
PNJ (`CallFunction_779` pose `AI` dans `LoopUpdateAI`, EG:25017-25037, puis `CallFunction_739` la repose dans `UpdateAI`).
- Validité de `AIDummy` au point d'insertion (point (a) de la fiche) : le `else` de `IfThenElse_148` n'est pris que si
`AIDummy.SaveSlot != 0` ; une lecture sur une référence nulle rend 0 (UE `ScriptCore.cpp:3234-3245`), donc `AIDummy`
n'est jamais nul quand H5-A s'exécute (établi par construction). Même source (`VariableGet_525`) que l'appel actuel.
- Le `.lpai` s'exécute en entier dans l'appel `PlayScene` : boucle `WhileLoop` `MacroInstance_963` sur les lignes, tant
que `Not(WaitingScene)` (EG:6027-6031, 6212-6223) ; `WaitingScene` ne passe à vrai qu'après une ligne « lourde »
(`LastHeavyFunction`, EG:20369-20390) = ligne commençant par `generate` ou `dress` (`GI__ReadSceneLine.txt:635-652`) ;
les 17 `.lpai` du jeu n'en ont pas (Grep `\.(generate|dress)|^\s*\w+\s*=\s*generate|\bdress\(`, `-i`, `*.lpai` sous
`Content\Modules` : 0).
- Gardes G1-G11 : avec `AI` = `[AIDummy]` valide, les tests `IsValidIndex` / `IsValid` des gardes qui lisent `AI` (G1, G2,
G3, G4, G5, G6, G7, G8, G9, G10, G11 selon la commande) passent par `then` : le chemin d'origine s'exécute. Le cas B ne
les déclenche plus ; elles restent utiles au cas A (conditions de `.lpscene`) et aux autres variables (ex. `SO` de
`so_follows.lpai:8-17`).
### 2.6 Alternatives comparées (fiche, point (c) ; aucune n'est tranchée ici)
| Variante | Où | Nœuds / coupures | Effet hors IA | Verdict de lecture |
|---|---|---|---|---|
| **F-a** (décidée) | corps de `MacroInstance_45` | 1 / 1 | aucun | la plus petite ; couvre tout `.lpai` synchrone |
| F-a′ : déplacer `CallFunction_739` dans la boucle | idem | 0 / 3 | aucun | même effet, plus de coupures, sans gain |
| F-b : exclusion mutuelle (`nextAi` ou `CheckingForScenes` dans une condition `Async`) | `MacroInstance_39` ou `_45` | 1-2 | cadence des scènes du joueur ou des routines ; une condition fausse fait sortir la macro (branche `IfThenElse_0` sur `Condition`, L6f:49499-49505 ; sortie exacte à lire avec `-Fn=Async -Exec`) au lieu de la mettre en pause | plus invasive |
| F-c : ne pas vider `Variables` pendant la boucle d'IA (Branch sur `nextAi` avant `Array_Clear`) | `UpdateCheckForScenes` | 2 / 1 | la vérification d'une scène candidate garderait les variables de la candidate précédente et des `.lpai` (ex. `Today`, `Current`, `Dest`) : appariement `WHO` des scènes du joueur modifié | risque visible sur les scènes du joueur |
| F-c′ : sauver puis reposer `AI` autour du `Array_Clear` | `UpdateCheckForScenes` | ≥ 4 (GetVariable, IsValidIndex + Branch, AddVariable, Make Array) | chemin chaud (5 490 `CHECKING:` en J3, `STE-VAL.md:364`) ; `AI` visible des candidates, sans lecteur (Grep `(^\|[^A-Za-z_])AI\.` sur `*.lpscene` : 0) | plus de nœuds, aucun gain pour le contenu actuel |
Lecture : aucune alternative n'est plus sûre que F-a sans effet de bord ; F-a reste conforme à la décision.
### 2.7 Témoin temporaire (Q-H5b-1) — complément A-CART pour l'orchestrateur LOT STE (2026-10-10)
Décision de l'utilisateur : Q-H5b-1 = témoin (`STE-TACHES.md:27`) : un `PrintString` temporaire sur le chemin « personne
vide » de G5, dans un build de diagnostic **non commité**, retiré avant le commit. Il remplace la variante (b) du § 4 (texte
et préfixe ci-dessous) ; la mesure « avant F-a » de cette variante devient la contre-épreuve C0 (fin de section).
Nœuds existants, graphe `CallFunction` de GI (**CFG** = `dumps\GI__CallFunction_apres-GARDES.txt`, 1563 nœuds, CFG:120 ;
état B8B97C46, graphe que l'IMPL H5b ne touche pas) :
| Nœud | Rôle | Liens (CFG) |
|---|---|---|
| `K2Node_IfThenElse_14` | G5-B1 : `IsValidIndex(BreakStruct_54.Person_24_…, 0)` (`CallArrayFunction_9`) | `execute` ← `CallFunction_189.then` ; `then` → `IfThenElse_15.execute` ; `else` **(vide)** (CFG:7619-7628) |
| `K2Node_IfThenElse_15` | G5-B2 : `IsValid(GetArrayItem_75.Output)` (`CallFunction_356`) | `execute` ← `IfThenElse_14.then` ; `then` → `CallFunction_190.execute` (`Map_Add(AI_Untils)`) ; `else` **(vide)** (CFG:7629-7637) |
| `K2Node_CallFunction_193` | modèle : `PrintString` « (test0104) for … hours by <clé> » après `_190` | `bPrintToScreen = 'true'`, `bPrintToLog = 'true'`, `WorldContextObject` (vide) (CFG:5586-5596) |
Nœuds créés (noms réels `K2Node_CallFunction_<n>`, notés à la création) :
- **T1** = `PrintString` sur `IfThenElse_14.else`, `InString` = `STE-H5b-TEMOIN G5-B1 setUntil Person vide` (variable
absente ou sans personne : le cas B que F-a supprime) ;
- **T2** = `PrintString` sur `IfThenElse_15.else`, `InString` = `STE-H5b-TEMOIN G5-B2 setUntil Person0 nul` (variable
présente, `Person[0]` nul : autre cause, hors F-a) ;
- pour les deux : `bPrintToScreen` = `false` (comme `CallFunction_606`, AV:329), `bPrintToLog` = `true` (défaut, relu) ;
`TextColor`, `Duration`, `Key`, `WorldContextObject` : défaut / vide (comme `CallFunction_193`). Texte ASCII seul ; préfixe
unique : `STE-H5b-TEMOIN` → 0 dans L3 (Grep, mode `count`), témoin de lecture du même fichier
`LogBlueprintUserMessages: \[GI_C_\d+\] \(test0104\)` → 46 ;
- ligne attendue : `LogBlueprintUserMessages: [GI_C_<n>] STE-H5b-TEMOIN G5-B1 setUntil Person vide` (préfixe `[objet]` quand
`Kismet.bLogPrintStringSource` est vrai, UE `Runtime\Engine\Private\KismetSystemLibrary.cpp:497-506`, observé pour les
messages existants de GI) ;
- `type_id` du `PrintString` : **non relu en 5.8** (absent de lp-mcp § 3 et de GIMPL § 2) ; il se lit à l'étape T-2.
Étapes MCP (une par appel ; `refPath` `/Game/BPs/GI.GI:CallFunction.K2Node_<Nom>`) :
- T-1. `get_node_infos` de `IfThenElse_14` et `IfThenElse_15` : liens = tableau ci-dessus (les deux `else` vides) ; sinon arrêt.
- T-2. `find_node_types {graph:{refPath:"/Game/BPs/GI.GI:CallFunction"}, type_id_filter:"PrintString", context_pins:[]}` ;
sans résultat, filtres `Print` puis `Imprimer`. Retenir l'entrée dont le nœud créé appelle `KismetSystemLibrary::PrintString`
(broches `InString`, `bPrintToScreen`, `bPrintToLog`, `TextColor`, `Duration`, `Key`), pas `PrintText` (`InText`). Aucune
entrée conforme → arrêt, pas de témoin, preuve par la variante (a) (V5 seul), rapport à l'orchestrateur.
- T-3. `create_node` T1 (position libre près de `IfThenElse_14`) ; `get_node_infos` T1 : nom, indices de `execute`,
`InString`, `bPrintToScreen` (attendu `execute` entrée 0 ; les autres relus, `self` / `WorldContextObject` cachés possibles).
- T-4. `connect_pins` : `IfThenElse_14.else` (sortie 1) → `T1.execute`.
- T-5. `set_pin_value` : `T1.InString` = `STE-H5b-TEMOIN G5-B1 setUntil Person vide`.
- T-6. `set_pin_value` : `T1.bPrintToScreen` = `false`.
- T-7 à T-10. Idem pour T2 sur `IfThenElse_15.else` (sortie 1), `InString` = `STE-H5b-TEMOIN G5-B2 setUntil Person0 nul`.
- T-11. `get_node_infos` de T1, T2, `IfThenElse_14`, `IfThenElse_15` : `else` → T1 / T2 seuls, `then` inchangés, `T1.then`
et `T2.then` vides, textes relus à l'octet.
- T-12. `compile_blueprint` (avertissements en erreurs) : 0 / 0 ; `save_assets {asset_paths:["/Game/BPs/GI"]}`.
Volume : 2 nœuds, 0 `break_pins`, 2 `connect_pins`, 4 `set_pin_value` ; `CallFunction` 1563 → 1565 nœuds (dump facultatif,
éditeur fermé : `.\Tools\RunCommandlet.ps1 -Lot STE-H5b -Commandlet LPDumpFunction -Args '-BP=GI -Fn=CallFunction -Pins -Exec'`).
Comportement : T1 et T2 sont sur des `else` vides et n'ont aucune sortie de données ; seule différence, une ligne de journal
(établi par construction).
Procédure (décision de l'utilisateur ; une session sur la copie, gel annoncé, BUILD-GO de Big Boss) :
1. IMPL H5b finie (§ 2.3, étapes 1-15) : GI enregistré, éditeur fermé (`.\Tools\UeState.ps1` = 0), MD5 **M1** relevé (V3),
dump V1 (EventGraph, 6234 nœuds) pris sur M1.
2. Instantané dédié : `.\Tools\Snapshot.ps1 -Lot STE-H5b-temoin -Path Content\BPs\GI.uasset` (lot distinct de `STE-H5b`, qui
garde B8B97C46 : la première sauvegarde d'un lot fait référence, `Tools\Snapshot.ps1:7-9`) ; MD5 de la copie
`D:\Game\LifePlay_Backup_5.5\STE-H5b-temoin\Content\BPs\GI.uasset` = M1, sinon arrêt.
3. Éditeur headless (lp-mcp § 2), `load_asset` `/Game/BPs/GI`, T-1 à T-12, fermeture, `UeState.ps1` = 0, MD5 **M2** (≠ M1,
relevé, jamais commité).
4. Build Development de diagnostic (mêmes commandes que `STF-T5-J-BUILD.md:8-10`) :
`.\Tools\Build.ps1 -Lot STE-H5b -Target LifePlay -Config Development -ExtraArgs "-NoUBA"` puis
`.\Tools\BuildDebug.ps1 -Config Development -ArchiveDir "D:\Game\LifePlay_Build_5.8_STE-H5b-TEMOIN_Development" -SkipBuild`
(dossier absent avant). `PrintString` est compilé en Development (retiré seulement en Shipping / Test, UE
`KismetSystemLibrary.cpp:458`).
5. J4 sur cette archive (parcours de V5, avec rafale) ; journal
`D:\Game\LifePlay_Build_5.8_STE-H5b-TEMOIN_Development\Windows\LifePlay\Saved\Logs\LifePlay.log`.
6. Retrait, éditeur fermé : `.\Tools\Restore.ps1 -Lot STE-H5b-temoin` (`-Lot` obligatoire, `Tools\Restore.ps1:19-24`) ;
MD5 = M1 ; `git status --short -- Content Config Source` = ` M Content/BPs/GI.uasset` seul ; V2 (`CompileAllBlueprints`)
sur M1. Seulement alors : ticket au gardien (commit de GI à M1). MD5 ≠ M1 → arrêt, pas de commit.
7. L'archive `..._STE-H5b-TEMOIN_Development` est gardée (jamais supprimée), marquée « diagnostic, GI M2 non commité » dans
le livrable J4 ; elle ne sert de référence à aucun autre lot.
Interdit pendant la fenêtre 2-6 : toute autre écriture de `GI.uasset` (G12 de STE-H3c en particulier), que la restauration
défairait. Si G12 doit être mesurée dans la même J4, son IMPL se place avant l'étape 2 (l'instantané couvre alors H5b + G12).
Écart assumé : J4 se joue sur M2, le commit porte M1 ; la seule différence est T1 / T2 (ci-dessus).
Critère (Grep en mode `count` sur le journal de J4) :
- `STE-H5b-TEMOIN` → **0** ; détail `STE-H5b-TEMOIN G5-B1` (cas B, critère de F-a) et `STE-H5b-TEMOIN G5-B2` ;
- non-vacuité, même journal : `hours by [1-9]` ≥ 1 (G5 atteinte avec une personne) et `LogBlueprintUserMessages: \[GI_C_\d+\]`
≥ 1 (les `PrintString` de GI arrivent au journal) ; rafale présente (V5) ;
- reste > 0 : rapport (compte, trois lignes de contexte par occurrence), pas de correctif. Attribution : `setUntil` est
appelé sur `AI` par les 17 `.lpai`, mais aussi par 6 `.lpscene` (`Actor.setUntil` × 5 sous `vin_Incest\Scenes\proactive`,
`CurrentCompanion.setUntil` dans `goodbye_companion.lpscene:11` ; Grep `\b\w+\.setUntil\b` `-i -o` sous `Content\Modules` :
23 lignes) ; la boucle d'IA ne tourne jamais pendant une scène du joueur (R5) : une ligne témoin pendant une scène n'est
pas imputable à F-a (contexte à lire, incertain).
Contre-épreuve sans F-a (**C0, jugée utile**) : depuis G5 (Q2), le cas B est muet et J3 ne dit pas combien il y en a eu ; un
0 en J4 seul ne distingue pas « F-a agit » de « parcours sans cas B » (sa fréquence dépend des rafales : 24 `by 0` dans L2,
H3S:151-152). C0 montre, avec le même témoin et un parcours comparable, que le cas B se produit sans F-a. Attendu C0 :
`STE-H5b-TEMOIN G5-B1` ≥ 1 (rapporté aussi au nombre de `CHECKING:`) ; J4 est alors lue à parcours comparable (`CHECKING:`
du même ordre). Coût : une écriture de GI, un build, une session et une restauration de plus (les 2 builds de la variante (b)
du § 4). Ordre conseillé, qui laisse intacte la procédure ci-dessus : juste après P4 (instantané `STE-H5b` = B8B97C46) et
avant l'IMPL H5b : T-1 à T-12 sur B8B97C46 (mêmes `refPath` : H5b ne touche pas `CallFunction`), build vers
`D:\Game\LifePlay_Build_5.8_STE-H5b-C0_Development`, session C0, `.\Tools\Restore.ps1 -Lot STE-H5b` éditeur fermé, MD5 =
`b8b97c46…`, puis IMPL H5b. Décision à l'utilisateur (elle ajoute un build à sa procédure) ; sans C0, le 0 de J4 est
rapporté « sans contrôle positif ».
## 3. Repli
- Avant écriture : instantané P4 (`LifePlay_Backup_5.5\STE-H5b`, état `b8b97c46…`).
- Échec de création, lien relu différent, refus non prévu (§ 2.3) ou compilation avec diagnostic : arrêt,
`.\Tools\Restore.ps1 -Lot STE-H5b` éditeur fermé (GI entier), contrôle MD5 = `b8b97c46b825a6af942baabd5bf74c56`.
- Après commit (gardien) : `git revert` du commit du ticket STE-H5b, ou tag `STE-H5b-avant` (ticket RESTORE).
- Indépendant des gardes : le repli de H5b ne défait aucune garde (instantané pris sur l'état B8B97C46).
## 4. Risques R#, questions Q#
- **R1** (A1) : sémantique d'`AddVariable` lue sur un dump 5.5 sans exécution (L6f:10070-10273). Attendu : remplacement par
nom sans casse. Indice concordant : si chaque appel ajoutait une entrée, chaque PNJ aurait deux `AI` (`_779` puis
`_739`) sans effet visible ; non probant seul. P3 tranche.
- **R2** (A2) : la branche `destroyDummy` d'`AddVariable` est vivante en 5.8 : 3 déclenchements dans L3 (Grep
`via AddVariable|DestroyDummy was from AddVariable| Add Variable` → 6 lignes, L3:8403-8404, 19888-19889, 20263-20264 ;
0 dans L2). Pour `AI` elle ne doit pas se déclencher : ancienne personne = nouvelle (`AIDummy`), et `SaveSlot != 0` au
point d'insertion (§ 2.5). Estimé fort ; P3 confirme ; en J4, ce compte ne doit pas croître (V5).
- **R3** (point (a)) : l'écrivain de `GI.AIDummy` n'est pas trouvé. EG : seuls `VariableSet_258` et `_276`, sur des chaînes
sans entrée d'exécution (EG:25011-25016, 25362-25376) ; L6f (GI 5.5 complet) : aucun autre `VariableSet` d'`AIDummy`
(Grep `AIDummy` : 39 lignes, hors EG seulement `destroyDummy` L6f:42252-42258, `destroySlot` L6f:43747-43750 et un nom
de variable de scène `'AIDummy'` dans `GenerateHomeAndWork`, L6f:43339, 43459) ; `PC__EventGraph.txt` : 0. Il est donc
posé hors de GI (autre Blueprint ou niveau, non dumpé). Non bloquant : `AIDummy` est non nul au point d'insertion
(§ 2.5), c'est la même lecture que `CallFunction_739`, et les routines valides l'impriment sous le nom d'objet `AI`
(`[AI] <n> moves to …` : 48 lignes dans L3, 9 dans L2 ; estimé : message de `Person_C::setCurrentLocation` sur `AIDummy`).
- **R4** : une ligne « lourde » (`generate…`, `dress…`) dans un futur `.lpai` suspendrait la routine (minuterie
`ContinueScene`) ; un vidage pourrait alors tomber entre deux lignes, hors de portée de F-a. Aujourd'hui : 0 cas (§ 2.5).
- **R5** (masquage, fiche § 5) : seule l'entrée nommée `AI` est remplacée (A1). Aucun contenu n'écrit `AI =` (Grep
`^\s*AI\s*=` `-i` sous `Content\Modules` : 0) ; aucune `.lpscene` ne lit `AI.` (0) ; les 17 `.lpai` la lisent (95 lignes).
La condition de `MacroInstance_45` contient `Not(SceneOn)` (`CallFunction_748`, EG:25586-25592) et `Len(followUp) == 0`
(`CallFunction_828`, EG:25979-25990) : H5-A ne s'exécute jamais pendant une scène du joueur. Chaque candidate de
`UpdateCheckForScenes` commence par `Array_Clear` : `AI` n'entre pas dans une vérification.
- **R6** (comportement modifié, accepté) : routines appliquées, moins de routines par PNJ, compteurs `ActorVars` avancés (§ 1).
- **R7** (preuve) : depuis les gardes (Q1 = A), une routine sans personne est **muette** (G4 et G5 sautent
`setCurrentLocation` et `setUntil` sans message) : le journal ne peut plus montrer directement « aucun vidage suivi de
routine sans personne ». La preuve directe est statique (§ 2.5) ; la preuve en jeu est statistique (V5) — voir Q-H5b-1.
- **R8** : variables laissées dans `Variables` par la vérification des scènes (après un vidage) et variables temporaires
des `.lpai` (`Today`, `Current`, `Dest`, `Until`) : comportement d'origine inchangé ; chaque `.lpai` recrée ses
temporaires avant de les lire (ex. `entertainment.lpai:7-16`, estimé pour les 16 autres).
- **R9** : réemploi de `MakeArray_3` à `_6` par deux appels : précédent G10 (`CallFunction_360` câblé sur les Make Array de
`CallFunction_203`, H3bS:159-160, GIMPL:33 ; compilé, J3 PASS). Un nœud pur est réévalué pour chaque appel qui le lit
(estimé fort, non relu dans `KismetCompiler.cpp`).
- **R10** : coût : un parcours de `Variables` par `.lpai` joué (≤ 17 par PNJ) ; négligeable (estimé).
**Q-H5b-1 (utilisateur, avant J4)** — preuve en jeu : (a) statistique seule, comparaison J4 / J3 (V5, défaut) ; ou
(b) témoin temporaire dans un build de diagnostic **non commité** : un `PrintString` « (test0104) setUntil sans personne »
sur `IfThenElse_14.else` (G5-B1, `CallFunction`, GIMPL:28), mesuré avant F-a (attendu > 0 dans une rafale) puis après
(attendu 0), retiré par `Restore.ps1` avant le commit (2 builds de plus, une écriture GI de plus).
## 5. Interdits
- Aucune modification hors du § 2.3 : pas de `delete_node`, pas de retouche de `CallFunction_739` / `_779`, de
`UpdateCheckForScenes` (`CallArrayFunction_5412`), des conditions des macros `Async`, des gardes G1-G11, du C++, du
contenu (`*.lpai`, `*.lpscene`).
- Aucun nœud AND / OR / Select ; aucune écriture HyperAIStudio (lp-mcp § 1.1) ; outils Epic seulement.
- Pas d'IMPL sans P1 à P6 (P3 compris) ; pas d'éditeur pendant un commandlet ou un build (lp-mcp § 1.7).
- Pas de commit par l'agent ni par l'IMPL : ticket au gardien (lp-repo) ; numéro CHG à réserver par l'orchestrateur
(`etat\REPO\NUMEROS.md`).
## 6. Valeurs moteur lues dans le moteur installé (UE 5.8)
- `Delay(0)` : `FDelayAction::UpdateOperation` retire le temps écoulé et ne déclenche la suite que lorsque
`TimeRemaining <= 0` (UE `Runtime\Engine\Public\DelayAction.h:27-30`) : la reprise est latente, traitée par le
gestionnaire d'actions latentes, jamais au milieu d'une chaîne d'exécution synchrone. La macro `Async` n'a pas d'autre
point de suspension que ce `Delay` (`CallFunction_2`, `Duration = 0`, L6f:49523-49527).
- Référence nulle : l'expression est sautée et la valeur rendue effacée (UE `Runtime\CoreUObject\Private\UObject\ScriptCore.cpp:3234-3245`,
message `Accessed None` l. 3208-3216) : `AIDummy.SaveSlot` vaudrait 0 sur un nul, donc `IfThenElse_148.then`.
- `UBlueprint::GetAllGraphs` parcourt aussi `MacroGraphs` (UE `Runtime\Engine\Private\Blueprint.cpp:1969-1991`) ;
`LPDumpFunction` filtre ce tableau par `Graph->GetName()` (`Source\LifePlayEditorTools\LPDumpFunctionCommandlet.cpp:131-158`) :
`-Fn=Async` est accepté (précédent : `=== GI :: Async (20 noeuds) ===`, L6f:49498).
## 7. Critères de VAL (commande → attendu)
1. **V1** — dump après IMPL :
`.\Tools\RunCommandlet.ps1 -Lot STE-H5b -Commandlet LPDumpFunction -Args '-BP=GI -Fn=EventGraph -Pins -Exec'` →
`=== GI :: EventGraph (6234 noeuds) ===` (6235 avec le repli de l'étape 6) ; diff avec EG : seulement le bloc H5-A et
les lignes de `IfThenElse_148.else`, `CallFunction_743.execute`, `MakeArray_3` à `_6` (`Array`), conformes au § 2.4.
2. **V2** — `compile_blueprint` sans diagnostic (IMPL) ; `CompileAllBlueprints` éditeur fermé : 0 erreur, 0 avertissement
nouveau (lp-validate).
3. **V3** — MD5 de `GI.uasset` ≠ `b8b97c46…`, relevé au CHG ; `git status --short -- Content Config Source` = ` M Content/BPs/GI.uasset` seul.
4. **V4** — P3 conforme (A1, A2, A3) et joint au lot (`dumps\GI__AddVariable.txt`).
5. **V5** — J4 en jeu (build Development, parcours de `STE-J3.md:10-16` **plus une rafale** : déplacement par la carte vers
un autre lieu, lignes `UPDATE TIME ARRIVED` / `LOAD ROOM`, et une action de plusieurs heures), Grep en mode `count` sur
le `LifePlay.log` de J4, J3 (L3) comme référence :
| Motif | Attendu J4 | J3 (L3) |
|---|---|---|
| `hours by 0` | 0 | 0 (`STE-VAL.md:360`) ; L2 : 24 |
| `hours by [^0]` / `CHECKING:` | ≥ 46 / 5 490 (0,0084) | 46 / 5 490 (`STE-VAL.md:363-364`) |
| `LogBlueprintUserMessages: \[AI\] ` / `CHECKING:` | ≥ 48 / 5 490 (0,0087) | 48 (rejoué) |
| `DestroyDummy was from AddVariable` | même ordre que J3 (pas de rafale liée aux routines) | 3 (rejoué) |
| `Person_24_4B712C904750AD4CE1580DBEBD9E7396' of length 0` / `CHECKING:` | ≤ 0,042 | 232 / 5 490 (`STE-VAL.md:361`) |
| `property Person in`, `CallFunc_Array_Get_Item_3[0-3] `, `_41 ` | 0 | 0 (`STE-VAL.md:356-358`) |
| `DestroyDummy was from CheckScenes, WHO unmatched` | > 0 (des vidages ont eu lieu pendant le parcours) | 106 (rejoué) |
Puis `& .\Tools\LogStats.ps1 -Compare <journal J4>,<L3>` : aucune famille d'erreur ou d'avertissement nouvelle. Les
ratios dépendent du parcours (R7) ; un ratio en baisse est rapporté, pas corrigé. Selon Q-H5b-1 (b) : témoin = 0.
6. **V6** — à l'œil (utilisateur) : comportement des PNJ en ville après un déplacement et en temps accéléré ; scènes du
joueur et interface inchangées (§ 1, « Ce que l'utilisateur verra »).
Fichiers à commiter (dépôt jeu, branche `ue5.8`, ticket au gardien) :
- A `Docs/Migration/etat/ST/STE/STE-H5b-SPEC.md`
Tours : 48 / 60
## Constats
- Établi : nœuds et liens du § 2.2 (EG) ; MD5 et propreté git de `GI.uasset` (commandes en tête) ; condition de
`MacroInstance_45` (5 termes, dont `Not(SceneOn)` et `Len(followUp) == 0`) ; `.lpai` exécutés dans `PlayScene` par une
`WhileLoop` sans suspension pour le contenu actuel ; flux de données d'`AddVariable` et de `Async` en 5.5 (L6f) ;
comptes rejoués sur L2 et L3 (ci-dessous).
- Estimé : sémantique d'exécution d'`AddVariable` (A1-A3, P3) ; nom d'objet `AI` de `AIDummy` ; réévaluation des nœuds purs ;
absence de ligne lourde dans `random.lpai:10` (la seule correspondance de `generate|dress` dans les `.lpai` avec le motif
large est une liste de lieux, `beauty.lpai:12` contient `hairdresser`).
- Incertain : écrivain de `GI.AIDummy` (hors GI, non cherché dans les autres Blueprints : aucun dump) ; sortie exacte
d'`Async` quand la condition devient fausse (utile à F-b seulement).
- Comptes (Grep, mode `count`) : L2 ` Add Variable|via AddVariable|DestroyDummy was from AddVariable` → 0 ; L2
`hours by|WHO unmatched|index SPAWN` → 339 (lecture du fichier vérifiée) ; L2 `LogBlueprintUserMessages: \[AI\] ` → 9 ;
L3 même motif → 48 ; L3 `via AddVariable|DestroyDummy was from AddVariable| Add Variable` → 6 ; L3 `WHO unmatched` → 106 ;
L3 `hours by [1-9]` → 46 ; `Content\Modules` : 17 `.lpai` (Glob), `AI.` dans `*.lpai` → 95, dans `*.lpscene` → 0,
`^\s*AI\s*=` → 0.
- Dumps 5.8 manquants : `AddVariable` (P3, bloquant), `Async` (facultatif), `GetVariable` (non nécessaire : F-a n'en dépend
pas ; flux 5.5 en L6f:10274). Aucun dump 5.8 n'a de broches d'exécution pour ces graphes (Glob `**/*AddVariable*`,
`**/*Async*` sous `etat\` : 0 ; seul L6f les contient, sans `-Exec`).
- Écart à la fiche : aucun. Point (b) traité par P3 (préalable bloquant) plutôt que par un arrêt de la SPEC, parce que
l'appel ajouté est identique à `CallFunction_739`, déjà exécuté à chaque PNJ.