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

45 KiB
Raw Blame History

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 (« Add Variable ») 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.