LifePlay-Revival/Docs/Migration/etat/ST/STA/STA-T2b-VILLE-DIAG.md

291 lines
31 KiB
Markdown
Raw Normal View History

Fiche STA-T2b-VILLE — 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
# STA-T2b-VILLE-DIAG : StreetMap_Static, Room.pickPresetWorld, familles (Script Msg) et hitchs MapMarkers (2026-10-09)
Hors gabarit : partiel, annoncé par la fiche. Une sous-section DIAG 1-5 par clé (K1 à K9), puis § Constats (tableau récapitulatif, clés non traitées, compte rendu `sk-protocol` § 3).
Lecture seule : ni jeu, ni éditeur, ni commandlet, ni build. Seul fichier écrit : ce livrable.
Abréviations : `CIBLE` = `D:\Game\LifePlay_Build_5.8_Development_U8_essai\Windows\LifePlay\Saved\Logs\LifePlay.log` ; `REF` = `D:\Game\LifePlay-master 5.5\Docs\Migration\etat\U58\U6\LifePlay_58_session2.log` ; `dumps\` = `D:\Game\LifePlay-master 5.5\Docs\Migration\etat\ST\STA\dumps\` ; `Src\` = `D:\Game\LifePlay-master 5.8\Source\LifePlay\` ; `E58\` = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\`.
Deux mécanismes moteur servent à toutes les clés (lus dans le moteur 5.8 installé) :
- Forme `Script Msg: Attempted to access index …` (sans ligne `Function`, sans pile) : émise par `UKismetArrayLibrary::GenericArray_Get` (nœud Get par copie) via `FFrame::KismetExecutionMessage` ; la valeur rendue est remise à la valeur par défaut (None, 0) et la fonction continue (`E58\Engine\Private\KismetArrayLibrary.cpp:459-480`). Cela confirme le point amont de STA-T1 § Écarts (pas de ligne `Function` pour ces événements).
- Forme `Accessed None trying to read (real) property P in <classe>` : P est la propriété objet qui vaut None et sert de contexte (`E58\CoreUObject\Private\UObject\ScriptCore.cpp:3203-3218`). Forme nue `Accessed None` : `Stack.MostRecentProperty` est nul (`ScriptCore.cpp:3220-3228`). Forme « pending kill or garbage » : objet détruit mais pas encore collecté (`ScriptCore.cpp:3194-3201`).
---
## K1. `StreetMap_Static_C:SearchByAmenitySafe` : `CurrentCityExpansion in GI_C` (9 / 14) et forme nue (1 / 1)
### 1. Symptôme et reproduction
- CIBLE:7898-7910 : `Accessed None trying to read (real) property CurrentCityExpansion in /Game/BPs/GI.GI_C`, `Function …StreetMap_Static_C:SearchByAmenitySafe:64X` ; pile : `CentralLondon_C:ReceiveTick` → `GI_C:UpdateTime` → `TopRight_C:GenerateRecs` → `Person_C:recommendActions` → `recommendKeyActions` → `recommendHomeIfNeeded` → `SearchByAmenitySafe`.
- CIBLE:51374-51386 : même message ; pile : `Scene_C` bouton Next → `GI_C:ContinueScene` → `ReadSceneLine` → `CallFunction` → `MakePermanent` → `Person_C:assignWork` → `SearchByAmenitySafe`.
- Forme nue : CIBLE:51388-51400, même fonction, même milliseconde (16.45.55:195) et même pile que 51374.
- Comptes (Grep `count`, motif `Warning: Accessed None trying to read \(real\) property CurrentCityExpansion`) : CIBLE 9, REF 16 (= 14 `SearchByAmenitySafe` + 2 `findNearbyBuilding`, `STA-T1-INV.md:135,151`).
### 2. Hypothèse testée
`GI.CurrentCityExpansion` vaut None parce qu'aucun emplacement de sauvegarde d'extension de ville n'existe, et `SearchByAmenitySafe` lit `CurrentCityExpansion.Buildings` sans test `IsValid`.
### 3. Essai et résultat brut
- Nœud fautif : `dumps\StreetMap_Static__SearchByAmenitySafe.txt:11-19` (`VariableGet 'GI'` → `VariableGet 'CurrentCityExpansion'` → `VariableGet 'Buildings'`) ; `Buildings` alimente `VariableSet_3` (Shuf, l.175-179, branche « Amenity non vide »), `CallArrayFunction_3` (`Array_Length`, l.155-158) et `FunctionResult_3` (l.169-171) (branche « Amenity vide », `IfThenElse_6` l.164-168). Les 47 nœuds ne contiennent aucun `IsValid` : les deux seuls `MacroInstance` sont les boucles (l.23-30, l.87-94).
- Écriture de la variable, `dumps\GI__EventGraph.txt` : événement à paramètre `CityName` (`CustomEvent_1141`, l.11365-11368) → `DoesSaveGameExist(CityName)` (l.11369-11375) → `IfThenElse_2072` (l.11376-11380) : `else` (et `CastFailed`, l.11397) → `VariableSet_4287 'CurrentCityExpansion'` avec entrée vide, donc None (l.11388-11393) ; `then` → `LoadGameFromSlot` → cast → `VariableSet_4288` (l.11381-11387, 11401-11406). Appelé par `LoadCityExpansion(CityName)` après `SortStreetMapData` (l.11411-11415, 13174-13177). L'objet n'est créé qu'à l'ajout d'un bâtiment (`CustomEvent_447`, `IsValid` → `Is Not Valid` → `CreateSaveGameObject` → `VariableSet_5455`, l.11323-11328, 11416-11426, 11437-11442) puis écrit par `SaveGameToSlot` (l.11443-11450).
- Glob `*.sav` dans `D:\Game\LifePlay_Build_5.8_Development_U8_essai\Windows\LifePlay\Saved\SaveGames` : 2 fichiers, `GameSettings.sav`, `Default.sav` ; aucun emplacement de ville.
- Effet en jeu, lu dans le dump : branche « Amenity vide » : `Array_Length(None)` = 0 → `FunctionResult_0` rend `StreetMap.Buildings` (l.159-174) ; branche « Amenity non vide » : `Shuf` vide → boucle vide → `Temp` (variable locale, l.60-63) vide → `IfThenElse_2.then` → boucle sur `StreetMap.Buildings` (l.82-86, 187-198). Le repli voulu a lieu.
### 4. Verdict : confirmée
Établi : la variable vaut None tant que la ville n'a pas d'extension sauvegardée, état normal d'une ville jamais modifiée. Le résultat de la fonction est juste (repli sur `StreetMap.Buildings`) ; l'avertissement est du bruit. Forme nue : même appel ; le nœud exact n'est pas départagé entre les lectures de `VariableSet_3` et `CallArrayFunction_3` (incertain) ; le garde ci-dessous couvre les deux.
Correctif proposé (Blueprint `StreetMap_Static`, fonction `SearchByAmenitySafe`), garde justifié :
- (a) entre `IfThenElse_5.then` et `VariableSet_3` : macro `IsValid` sur `GI.CurrentCityExpansion` ; `Is Valid` → `VariableSet_3` ; `Is Not Valid` → `VariableSet_4` (boucle `StreetMap.Buildings`, `Temp` est encore vide).
- (b) entre `IfThenElse_5.else` et `IfThenElse_6` : même `IsValid` ; `Is Valid` → `IfThenElse_6` ; `Is Not Valid` → `FunctionResult_0`.
- Même garde dans `findNearbyBuilding` (REF 2, non dumpée).
- Non recommandé : créer un objet vide dans `LoadCityExpansion` au lieu de None ; `SaveGameToSlot` (l.11443-11450) écrirait alors un emplacement de ville sans changement voulu.
### 5. Hypothèse suivante
Aucune. Suite : correction Blueprint (N2 lp-impl), puis VAL N1 : rejouer le parcours CIBLE ; attendu : 0 ligne `property CurrentCityExpansion`, 0 forme nue sous `SearchByAmenitySafe`.
---
## K2. `StreetMap_Static_C:ExecuteUbergraph_StreetMap_Static` : `BGA in StreetMap_Static_C` (5 / 6, + 4 « pending kill » en REF)
### 1. Symptôme et reproduction
- CIBLE:7446-7462 : `Accessed None … property BGA` ; pile : `Action_C` bouton → `GI_C:InterruptAnyCurrentAction` → `StopDurationUpdate` → `MatchCurrentPlayerBuilding` → `Room_C:ShowCurrentPlayerBuilding` → `StreetMap_Static_C:PlayBGA`.
- CIBLE:7741-7756 : pile `GI_C:TakeActionWithBuilding` → `StreetMap_Static_C:PlayerGoHere` → `UpdateWall` → `PlayBGA` → `ExecuteUbergraph_StreetMap_Static`.
- CIBLE:7974-7978 : pile réduite à `ExecuteUbergraph_StreetMap_Static`.
- REF:8942-8953 : `Attempted to access AudioComponent_2147460819 via property BGA, but … is not valid (pending kill or garbage)`.
- Comptes (Grep `count`) : CIBLE 5 ; REF 10 (motif `property BGA|via property BGA` = 6 Accessed None + 4 pending kill, `STA-T1-INV.md:137,191`).
### 2. Hypothèse testée
`BGA` (composant audio) vaut None avant le premier `SpawnSound2D` (et redevient None après la collecte du composant auto-détruit) ; l'événement `PlayBGA` lit `BGA.Sound` et appelle `BGA.Stop()` sans `IsValid`.
### 3. Essai et résultat brut (`dumps\StreetMap_Static__EventGraph.txt`)
- Événement à paramètre `Amenity` (`CustomEvent_13`, l.5417-5420 ; nom `PlayBGA` estimé : appel `?::PlayBGA()` à paramètre `Amenity`, l.5453-5457, et pile CIBLE:7755-7756) → `PlayStreetBGA = false` (l.5486-5491) → `IfThenElse_22` sur `Map_Find(AmenityBGAs, Amenity)` (l.5492-5505).
- `else` → `CallFunction_134` `BGA.Stop()` (l.5443-5446) : lecture 1.
- `then` → `IfThenElse_23` sur `EqualEqual_ObjectObject(Value, BGA.Sound)` (`VariableGet_130`, self = BGA, l.5458-5460, 5506-5515) : lecture 2 ; `else` → `SwitchString_3` : `street` → `PlayStreetBGA = true` → `RetriggerableDelay 0.5` → `IfThenElse_21` → `CallFunction_124` (l.5465-5485) ; `Default` → `CallFunction_124` `BGA.Stop()` (l.5461-5464) : lecture 3 → `SpawnSound2D(bAutoDestroy = true)` → `VariableSet_32 'BGA'` (l.5421-5439).
- Grep `'BGA'` sur le dump : 2 nœuds seulement (`VariableSet_32` l.5434, `VariableGet_129` l.5440) : un seul écrivain, aucun `IsValid`.
- CIBLE:7977-7978 (une seule trame `ExecuteUbergraph`) : reprise latente du `RetriggerableDelay` de la branche `street` (estimé).
- REF « pending kill » : `bAutoDestroy = 'true'` (l.5432) ; composant détruit à la fin du son, encore référencé avant le GC (estimé) ; après le GC la référence vaut None (estimé).
### 4. Verdict : confirmée
Établi : 3 lectures de `BGA` sans garde. Bénin : `Stop` sur None ne fait rien et la comparaison rend faux, donc le son est lancé quand même.
Correctif proposé (Blueprint `StreetMap_Static`, graphe d'événements), garde justifié (None est l'état initial) :
- (a) `IfThenElse_22.then` → `IsValid(BGA)` ; `Is Valid` → `IfThenElse_23` ; `Is Not Valid` → `SwitchString_3.execute`.
- (b) `IfThenElse_22.else` → `IsValid(BGA)` ; `Is Valid` → `CallFunction_134` ; `Is Not Valid` → fin.
- (c) devant `CallFunction_124` (deux entrées : `SwitchString_3.Default`, `IfThenElse_21.then`) → `IsValid(BGA)` ; `Is Valid` → `CallFunction_124` ; `Is Not Valid` → `CallFunction_110` (`SpawnSound2D`).
`IsValid` écarte aussi les objets « pending kill or garbage », donc les 4 lignes REF.
### 5. Hypothèse suivante
Aucune. VAL N1 : parcours ville (clic bâtiment, PlayerGoHere, rue) ; attendu : 0 ligne `property BGA` ou `via property BGA`.
---
## K3. `Room_C:pickPresetWorld` : index hors bornes `BuildingPoints` (4 / 10)
### 1. Symptôme et reproduction
- CIBLE:7465-7483 : `Attempted to access index 1 from array BuildingPoints of length 0!` ; pile `Action_C` → `GI_C:InterruptAnyCurrentAction` → … → `Room_C:ShowCurrentPlayerBuilding` → `ShowCurrentWorldPreset` → `pickPresetWorld` ; CIBLE:7485-7503 : index 0, même milliseconde.
- CIBLE:7759-7791 : même paire ; pile `TakeActionWithBuilding` → `PlayerGoHere` → `UpdateWall` → `Room_C:ShowCurrentWorldPreset` → `pickPresetWorld`.
- CIBLE:7505 et 7795, juste après chaque paire : `[Room_C_0] Modules/vin_Base/World/street___vin01.lpworld test0203`.
- Comptes (Grep `count`, `Warning: Attempted to access index \d+ from array BuildingPoints`) : CIBLE 4, REF 10.
### 2. Hypothèse testée
Le bâtiment passé n'a pas de polygone (joueur dans la rue), et `pickPresetWorld` lit `BuildingPoints[0]` et `[1]` sans test de longueur quand `BackgroundsSaved` ne connaît pas le bâtiment.
### 3. Essai et résultat brut (`dumps\Room__pickPresetWorld.txt`)
- Entrée `(Building, Amenity)` → `GI.BuildingPointsToString(BuildingPoints)` → `Map_Find(GI.BackgroundsSaved)` → `IfThenElse_0` : trouvé → `FunctionResult_1` ; sinon `VariableSet_1 len = Array_Length(WorldPresets[Amenity].Strings)` (l.2-13, 106-112, 136-158).
- `GetArrayItem_0` (index 0) et `GetArrayItem_1` (index 1) sur `BuildingPoints` (l.20-23, 36-39) → graine `Round(P0.X + P0.Y + P1.X + P1.Y)` (l.29-35, 51-58) → `FMod(graine, len)` → `Abs` → `Round` → `Strings[i]` (l.45-50, 63-66, 83-86, 113-116) → `Select(Strings[i], Strings[0], Len > 0)` (l.117-135) → `PrintString(… + ' test0203')` (l.67-82). Aucun test de longueur sur `BuildingPoints`.
- `BuildingPoints` est un membre natif de `FStreetMapBuilding` (`D:\Game\LifePlay-master 5.8\Plugins\StreetMap\Source\StreetMapRuntime\Public\StreetMap.h:300-302`).
- Grep `\[Room_C_0\] .* test0203` sur CIBLE : 7 lignes (7505, 7795, 7994, 8033, 37861, 49553, 51570) ; seules les deux premières (rue) suivent des avertissements ; la 3e ligne `street` (8033) n'en a pas, donc le bâtiment passé y avait des points.
### 4. Verdict : confirmée
Établi : `BuildingPoints` de longueur 0 avec un préréglage `street` ; tableau vide normal (rue, aucun bâtiment). Effet : la graine vaut 0 et le préréglage retenu est `Strings[0]` (`street___vin01`), choix fixe sans défaut visible (estimé).
Correctif proposé (Blueprint `Room`, fonction `pickPresetWorld`), garde justifié : `Branch` sur `Array_Length(BuildingPoints) >= 2` entre `IfThenElse_0.else` et `VariableSet_1` ; vrai → chemin actuel ; faux → `VariableSet_2 string = GetArrayItem_3` (`Strings[0]`) → `PrintString` → `FunctionResult_0`. Risque voisin, non observé : `len = 0` (aucun préréglage pour l'amenity) donne `FMod` par 0 (l.45-48) ; le même `Branch` peut tester `len > 0`.
### 5. Hypothèse suivante
Aucune. VAL N1 : rue au chargement ; attendu : 0 ligne `array BuildingPoints`, préréglage `street___vin01` inchangé.
---
## K4. `GI_C:(Script Msg)` : `Person_24 in Variable` (39 / 59)
### 1. Symptôme et reproduction
- CIBLE:38010-38022 : `Script Msg: Attempted to access index 0 from array 'Person_24_4B71…' of length 0 in '/Game/Structs/Variable.Variable'!`, `called by: GI_C`, suivi à la même milliseconde de `Accessed None trying to read (real) property CallFunc_Array_Get_Item in not an UClass`, `Function …GI_C:clearVariable:64X` ; pile `GI_C:UpdateCheckForScenes` → `CheckConditionsWho` → `ReadSceneLine` → `CallFunction` → `getPerson`.
- CIBLE:38063-38075 : idem ; pile se terminant par `GI_C:getRelative`.
- Comptes (Grep `count`, `Script Msg: Attempted to access index \d+ from array 'Person_24_`) : CIBLE 39 ; REF 59 (`STA-T1-INV.md:121`, cohérent avec le compte REF regroupé du § Constats).
### 2. Hypothèse testée
`clearVariable` lit `GetVariable(Var).Person[0]` sans test de longueur, pour une variable de scène qui ne contient aucune personne.
### 3. Essai et résultat brut (`dumps\GI__clearVariable.txt`)
- `GetVariable(Var)` → `BreakStruct Variable` → `Person_24_…` → `GetArrayItem_0`, index 0 (l.5-24). La sortie sert à `EqualEqual(Companion)`, `EqualEqual(Interacting)`, `Prefix`, `SaveSlot`, `Name_First`, `OccupyingMoreVar`, `DestroyPerson` (l.24, 39-151). Seul test : `Len(Var) > 0` (l.25-38) ; aucun test sur la longueur du tableau.
- Grep avec contexte (`CallFunc_Array_Get_Item in not an UClass`, `-B 1 -A 2`) : chaque `Script Msg Person_24` est suivi d'un ou deux `Accessed None` de `clearVariable` à la même milliseconde (CIBLE:48954-48968 : deux ; CIBLE:38010-38012 : au moins un).
### 4. Verdict : confirmée
Établi : même événement que la famille `GI_C:clearVariable` (72 / 94, `STA-T1-INV.md:117`, fiche STA-T2b-GIVAR). Variable vide = état possible d'une condition de scène (estimé). Remarque : si `Companion` vaut None, `None == None` rend vrai et la fonction ne fait rien (l.46-63, `IfThenElse_1.then` vide).
Correctif proposé (Blueprint `GI`, fonction `clearVariable`), garde justifié : `Branch` sur `Array_Length(Person_24) > 0` entre `CallFunction_0.then` (GetVariable) et `IfThenElse_1` ; faux → retour. Supprime le `Script Msg` et les `Accessed None` qui le suivent (la part exacte des 72 reste à compter par GIVAR). À fusionner avec la proposition de STA-T2b-GIVAR (même nœud).
### 5. Hypothèse suivante
Aucune pour la clé. Consolidation STA-T2c : une seule correction GIVAR + VILLE.
---
## K5. `GI_C:(Script Msg)` : `Texture2dArray_3 in Te2dArray` (6 / 0)
### 1. Symptôme et reproduction
- CIBLE:51230-51231 : `Script Msg: Attempted to access index 0 from array 'Texture2dArray_3_E303…' of length 0 in '/Game/Structs/Te2dArray.Te2dArray'!`, `called by: GI_C` (trame 954, début de la scène de couple, juste avant `Show couple : branche` CIBLE:51232).
- CIBLE:51255-51256 : trame 752, juste après `[SexAction_C_…] MF_sp01 (Animation String)` (CIBLE:51254). Autres : CIBLE:51290, 51307, 51322, 51346 (Grep : 6 lignes).
- Pas de pile (forme `Script Msg`).
### 2. Hypothèse testée
Une fonction de `GI` lit `Te2dArray.Texture2dArray[0]` sur un tableau vide au changement d'animation de la scène de couple.
### 3. Essai et résultat brut
- Grep `Texture2dArray` : `dumps\GI__EventGraph.txt` 0 ; `dumps\GI__graphes.txt` 0 (noms de graphes seulement) ; `dumps\Person__EventGraph.txt` 0.
- « called by: GI_C » (instance) : fonction propre à `GI` hors graphe d'événements, non dumpée (estimé).
- Grep `Te2dArray` sur `Content\**\*.uasset` : 0, non concluant (ripgrep saute les fichiers binaires).
### 4. Verdict : indécise (nœud non localisé)
Nouvelle en CIBLE parce que REF ne joue pas la scène de couple (`STA-T1-INV.md:258`). Correctif générique, à confirmer sur le nœud : `Array_IsValidIndex(Texture2dArray, 0)` avant le Get ; sinon texture par défaut ou rien.
### 5. Hypothèse suivante (N0 puis N3)
N0 lp-builder : `LPDumpFunction -BP=GI -Fn=<f> -Pins -Exec` sur les candidats proches dans le temps (`PlayAppropriateSexAnimation`, `PlayAnimationMorph`, `PersonPlayAnimationMorph`, `getFaceTexture`, `getMID`, `CumSound` : `dumps\GI__graphes.txt:80,8,81,267,26,269`), puis Grep `Texture2dArray_3`. N3 ensuite pour le correctif.
---
## K6. `SexInterface_C:(Script Msg)` : `SexPersons in GI_C` (3 / 0)
### 1. Symptôme et reproduction
CIBLE:51235-51240 : `Script Msg: Attempted to access index 2`, puis `3`, puis `4`, `from array 'SexPersons' of length 2 in '/Game/BPs/GI.GI_C'!`, même milliseconde (16.44.17:984), `called by: SexInterface_C …GI_C_2147482540.SexInterface_C_2147464127`. Pas de pile.
### 2. Hypothèse testée
`SexInterface` lit `GI.SexPersons` aux indices fixes 0 à 4 (cinq emplacements) sans test de longueur ; avec 2 participants, 3 lectures hors bornes.
### 3. Essai et résultat brut
- `SexInterface` n'a pas été dumpé ; l'asset existe (Glob : `D:\Game\LifePlay-master 5.8\Content\UI\SexInterface.uasset`).
- Indices 2, 3, 4 consécutifs dans la même milliseconde : lectures à index fixe (estimé). Le Get rend None (`KismetArrayLibrary.cpp:479`).
### 4. Verdict : indécise (nœud non localisé), mécanisme estimé
Bénin pour une scène à deux. Correctif générique : `Array_IsValidIndex(GI.SexPersons, i)` devant chaque emplacement, ou boucle `ForEach` sur `SexPersons`.
### 5. Hypothèse suivante (N0 puis N3)
N0 : `LPDumpFunction -BP=SexInterface` (liste des graphes), puis dump de la fonction qui lit `SexPersons`. N3 ensuite.
---
## K7. `Person_C:(Script Msg)` : `Standspots_Occupied in Room_C` (15 / 9)
### 1. Symptôme et reproduction
- CIBLE:7635-7636 : `Script Msg: Attempted to access index 0 from array 'Standspots_Occupied' of length 0 in '/Game/BPs/Room.Room_C'!`, `called by: Person_C …PlayerCharacter`, juste après une pile terminée par `Person_C:standIdle` (CIBLE:7633).
- CIBLE:19503 (index 0, longueur 0), 19573 (index 1, longueur 1), 19625 (index 2, longueur 2) : index = longueur à chaque fois.
- Comptes (Grep `count`) : CIBLE 15 ; REF 9 (`STA-T1-INV.md:134`).
### 2. Hypothèse testée
`standIdle` parcourt `Room.Standspots` et lit le tableau parallèle `Standspots_Occupied[i]` ; celui-ci est rempli au fil de l'eau (`Array_Set` avec `bSizeToFit`), donc plus court que `Standspots`.
### 3. Essai et résultat brut (`dumps\Person__standIdle.txt`)
- `ForEach` sur `GI.Room.Standspots` (`MacroInstance_1`, l.171-181) → `GetArrayItem_0` `Standspots_Occupied[Array Index]` (l.182-188) → `IsValid` (`MacroInstance_3`, l.189-193) → `Valid` (l.197-204) ; `Is Not Valid` ou non `Valid` → `Knot_1` → `Dest` = emplacement libre (l.194-196, 205-209).
- Écriture dans la même fonction : `Array_Set(Standspots_Occupied, Find(Standspots, Dest), self, bSizeToFit = 'true')` (l.144-162).
### 4. Verdict : confirmée
Établi : tableau parallèle volontairement creux ; le Get rend None et l'emplacement est vu comme libre, ce qui est le comportement voulu. Bénin.
Correctif proposé (Blueprint `Person`, fonction `standIdle`), garde justifié : `Branch` sur `Array_IsValidIndex(Standspots_Occupied, Array Index)` avant `MacroInstance_3` ; faux → `Knot_1` (libre). Autre voie : dimensionner `Standspots_Occupied` à `Array_Length(Standspots)` là où `Room` remplit `Standspots` (`Room` non dumpé au-delà de `pickPresetWorld`).
### 5. Hypothèse suivante
Aucune. Les `Accessed None` qui suivent (`OccupyingFnt`, `Near`) relèvent de STA-T2b-PERSON.
---
## K8. `GI_C:(Script Msg)` : `WidgetComponents in MapMarkers_C` (1 / 2)
### 1. Symptôme et reproduction
- CIBLE:7380-7399 : `Script Msg: Attempted to access index 0 from array 'WidgetComponents' of length 0 in '/Game/BPs/MapMarkers.MapMarkers_C'!` (`called by: GI_C`), puis `Accessed None … CallFunc_Array_Get_Item_53 in not an UClass` (`ExecuteUbergraph_GI`) ; pile `MainMenu_C` Continue → `GI_C:DelayedStart` → `PC_C:BeginAutoLoad` → `GI_C:AutoLoad` → `MoveCity` → `SpawnMapMarkers` → `FocusOnMapMarkers`.
- CIBLE:7413, 75 ms plus tard : `LogLifePlay: Display: MapMarkers : slot de focus WidgetComponents[0] ajoute (ERR-008, …)`.
- REF:7451 et REF:8172 (Grep : 2).
### 2. Hypothèse testée
`FocusOnMapMarkers` lit `MapMarkers.WidgetComponents[0]` juste après l'apparition des marqueurs, avant que la garde C++ (tick de 0,25 s) n'insère son emplacement de focus.
### 3. Essai et résultat brut
- `dumps\GI__EventGraph.txt:13185-13212` : événement sans paramètre (`CustomEvent_183`) → `SetInputMode_GameAndUIEx(PC, MapMarkers.WidgetComponents[0].GetUserWidgetObject())` ; seule lecture de `WidgetComponents` dans le graphe d'événements de `GI` (Grep : 1 `VariableGet`, l.13191). Nom `FocusOnMapMarkers` estimé (appel l.13213-13216 et pile CIBLE:7398-7399).
- Garde : `Src\LPMapMarkersGuardSubsystem.cpp:61` (tick 0,25 s), remplit le tableau seulement s'il est vide (l.89-125) ; en-tête `Src\LPMapMarkersGuardSubsystem.h:1-4` (« GI.FocusOnMapMarkers lit MapMarkers.WidgetComponents[0] sans test de longueur », « Aucun Blueprint n'est modifié »).
- Un événement par instance de `MapMarkers_C` : CIBLE 1 instance (2147481658), REF 2 instances (2147481693, 2147479394, d'après les lignes de hitch).
### 4. Verdict : confirmée
Reste d'ERR-008 : une lecture par apparition de `MapMarkers`, sans plantage (`SetInputMode` avec widget None).
Correctif proposé (Blueprint `GI`, `CustomEvent_183`), garde justifié : `Branch` sur `Array_IsValidIndex(MapMarkers.WidgetComponents, 0)` après l'événement ; faux → `SetInputMode_GameAndUIEx` sans widget (comportement actuel), ou rien. Alternative C++ (garde lancée à l'apparition de l'acteur) non recommandée : un emplacement en tête avant les marqueurs risque de décaler les index par rapport à `Vector2Ds` (estimé, `LPMapMarkersGuardSubsystem.h:3-4`).
### 5. Hypothèse suivante
Aucune. Lien avec K9 : si ce correctif Blueprint est fait, la création du composant de focus par la garde (cause de K9) peut devenir inutile ; à décider (§ Constats, Q1).
---
## K9. Hitchs `MapMarkers_C.LPFocusWidgetComponent` (2 / 4)
### 1. Symptôme et reproduction
- CIBLE:7972 (1,77 ms, trame 330, action « aller à ») et CIBLE:51241 (11,66 ms, trame 955, début de la scène de couple) : `LogUObjectGlobals: Warning: Gamethread hitch waiting for resource cleanup on a UObject (WidgetComponent …MapMarkers_C_2147481658.LPFocusWidgetComponent) overwrite took …ms`.
- REF:7736 (3,45 ms), 8321 (6,07), 9687 (3,14), 14480 (3,43).
- Comptes (Grep `count`, `LPFocusWidgetComponent\) overwrite took`) : CIBLE 2.
### 2. Hypothèse testée
La garde recrée son composant de focus avec un nom fixe alors que le précédent (détruit par le Blueprint, pas encore collecté) existe encore dans le même acteur : `NewObject` reconstruit par-dessus et attend la fin du nettoyage côté rendu.
### 3. Essai et résultat brut
- `Src\LPMapMarkersGuardSubsystem.cpp:127` : `NewObject<UWidgetComponent>(Markers, TEXT("LPFocusWidgetComponent"))`, nom fixe. Ligne atteinte seulement si, après retrait des entrées invalides, le tableau est vide (l.89-101) et si aucun `UWidgetComponent` valide ne reste sur l'acteur (l.106-125). Grep `LPFocusWidgetComponent` sur `Source` : seul ce fichier (l.127 et 147).
- Moteur : `StaticAllocateObject` cherche un objet de même nom dans le même outer (`E58\CoreUObject\Private\UObject\UObjectGlobals.cpp:3515-3516`), le détruit (`ConditionalBeginDestroy`, l.3687-3691), boucle tant que `IsReadyForFinishDestroy` est faux (l.3693-3705), puis écrit l'avertissement (l.3706-3710). `UPrimitiveComponent::IsReadyForFinishDestroy` attend `DetachFence` (`E58\Engine\Private\Components\PrimitiveComponent.cpp:2050-2054`).
- Ce que mesure « overwrite took » : le temps passé par le thread de jeu à attendre que le thread de rendu libère le proxy de l'ancien composant avant de reconstruire l'objet au même nom. Plus lourd en scène chargée (11,66 ms en scène de couple).
### 4. Verdict : confirmée
Mécanisme établi par le code. Qui détruit l'ancien composant : `HideAllMarkers` du Blueprint (estimé, `LPMapMarkersGuardSubsystem.h:2` et absence de composant valide à la l.107).
Correctif proposé (C++, `Src\LPMapMarkersGuardSubsystem.cpp:127`) :
```cpp
const FName FocusName = MakeUniqueObjectName(Markers, UWidgetComponent::StaticClass(), TEXT("LPFocusWidgetComponent"));
UWidgetComponent* Focus = NewObject<UWidgetComponent>(Markers, FocusName);
```
`MakeUniqueObjectName` a la même signature en 5.5 (`D:\Game\Launcher\Engine\UE_5.5\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectGlobals.h:972`) et en 5.8 (`E58\CoreUObject\Public\UObject\UObjectGlobals.h:1062`), donc pas de garde de version. La fonction boucle tant qu'un objet de ce nom existe et ne rend jamais le nom de base (`UObjectGlobals.cpp:2715-2728`) : plus de reconstruction par-dessus, l'ancien objet part au GC normal. Autre voie (Q1) : correctif Blueprint K8, puis retrait de la création du composant de focus (garder le vidage de `Vector2Ds`, l.104).
### 5. Hypothèse suivante
Aucune. Suite : IMPL N2 (1 ligne C++), BUILD, puis VAL en jeu ; attendu : 0 ligne `overwrite took` sur `LPFocusWidgetComponent`, ligne de garde `slot de focus` toujours présente, plus aucune lecture hors bornes de `WidgetComponents` après la première.
---
## Constats
### Tableau récapitulatif
| Clé | Famille (T1) | CIBLE / REF | Preuve (nœud ou source) | Pile (journal) | Cause | Verdict | Correctif proposé | Effort |
|---|---|---|---|---|---|---|---|---|
| K1 | `StreetMap_Static_C:SearchByAmenitySafe` | 9+1 / 14+1 | `SearchByAmenitySafe.txt:11-19,175-179` ; `GI__EventGraph.txt:11376-11393` | CIBLE:7898-7910, 51374-51400 | `CurrentCityExpansion` None sans extension de ville, lu sans `IsValid` | confirmée (forme nue : nœud incertain) | 2 `IsValid` (BP) | faible |
| K2 | `StreetMap_Static_C:ExecuteUbergraph_StreetMap_Static` | 5 / 6 (+4) | `StreetMap_Static__EventGraph.txt:5417-5515` | CIBLE:7446-7462, 7741-7756 ; REF:8942 | `BGA` None ou détruit, lu 3 fois sans `IsValid` | confirmée | 3 `IsValid` (BP) | faible |
| K3 | `Room_C:pickPresetWorld` | 4 / 10 | `Room__pickPresetWorld.txt:20-39` | CIBLE:7465-7503, 7759-7791 ; preset CIBLE:7505 | rue sans polygone, `BuildingPoints[0..1]` sans test | confirmée (tableau vide normal) | 1 `Branch` (BP) | faible |
| K4 | `GI_C:(Script Msg)` Person_24 | 39 / 59 | `GI__clearVariable.txt:5-24` | CIBLE:38010-38022, 38063-38075 | variable sans personne, `Person[0]` sans test | confirmée (même événement que GIVAR) | 1 `Branch` (BP, avec GIVAR) | faible |
| K5 | `GI_C:(Script Msg)` Texture2dArray_3 | 6 / 0 | non localisé (Grep 0 dans les dumps GI) | CIBLE:51230, 51255 (sans pile) | lecture `[0]` d'un `Te2dArray` vide (estimé) | indécise | `IsValidIndex` (à poser sur le nœud) | dump N0 d'abord |
| K6 | `SexInterface_C:(Script Msg)` | 3 / 0 | non dumpé | CIBLE:51235-51240 (sans pile) | index fixes 2-4 pour 2 participants (estimé) | indécise | `IsValidIndex` ou boucle | dump N0 d'abord |
| K7 | `Person_C:(Script Msg)` Standspots | 15 / 9 | `Person__standIdle.txt:144-162,171-209` | CIBLE:7633-7636, 19503-19625 | tableau parallèle creux (`bSizeToFit`) | confirmée (bénin) | 1 `Branch` (BP) | faible |
| K8 | `GI_C:(Script Msg)` WidgetComponents | 1 / 2 | `GI__EventGraph.txt:13185-13212` ; `LPMapMarkersGuardSubsystem.cpp:61,89-125` | CIBLE:7380-7399, 7413 | lecture avant le tick de garde | confirmée | 1 `Branch` (BP) | faible |
| K9 | hitch `LPFocusWidgetComponent` | 2 / 4 | `LPMapMarkersGuardSubsystem.cpp:127` ; `UObjectGlobals.cpp:3515-3710` | CIBLE:7972, 51241 | `NewObject` à nom fixe sur un objet détruit non collecté | confirmée | `MakeUniqueObjectName` (C++, 1 ligne) | faible + BUILD |
Recompte (Grep `count`, une commande par clé, motifs cités dans chaque § 1) : CIBLE K1 9, K2 5, K3 4, K4 39, K7 15, K8 1, K9 2 ; K5 6 et K6 3 (lignes listées par Grep). REF K1 16, K2 10, K3 10 ; motif regroupé `Script Msg: … array '(Person_24_|Standspots_Occupied|WidgetComponents|Texture2dArray_3_|SexPersons)` = 70 = 59 + 9 + 2 + 0 + 0 (`STA-T1-INV.md:121-123,134,143`). Tous égaux aux comptes de STA-T1.
### Clés non traitées ou partielles
- K5 : nœud non localisé (fonction `GI` non dumpée) ; K6 : `SexInterface` non dumpé. Correctif générique seulement.
- K1, forme nue (1 / 1) : même appel établi ; nœud exact non départagé.
- Hors périmètre, cités pour mémoire : `StreetMap_Static_C:findNearbyBuilding` (REF 2, même clé que K1, même garde) ; `Accessed None` de `clearVariable` (GIVAR), `OccupyingFnt` et `Near` (PERSON), `CallFunc_Array_Get_Item_53` (GIUBER) ; `Divide by zero` (SexAction_C, Room_C).
### Établi / estimé / incertain
- Établi : nœuds et liens cités (dumps:ligne), piles (journal:ligne), comptes (Grep), code de la garde et mécanismes moteur (sources 5.8 installées), absence d'emplacement de ville (Glob).
- Estimé : noms des événements personnalisés (`PlayBGA`, `FocusOnMapMarkers`, `LoadCityExpansion`) déduits des appels et des piles, car le dump ne nomme pas les `CustomEvent` ; destruction du composant de focus par `HideAllMarkers` ; références nulles après le GC (K2) ; mécanismes K5 et K6.
- Incertain : nœud de la forme nue (K1) ; fonction `GI` de K5 ; fonction `SexInterface` de K6. Essais : dumps N0 indiqués en K5 et K6.
### Questions pour l'utilisateur ou la SPEC
- Q1 (K8/K9) : garder la garde C++ et corriger seulement le nom (`MakeUniqueObjectName`), ou corriger `FocusOnMapMarkers` en Blueprint et retirer la création du composant de focus ? La première voie ne touche aucun Blueprint (principe de `LPMapMarkersGuardSubsystem.h:4`).
- Q2 (K4) : une seule fiche IMPL pour `clearVariable` (GIVAR + VILLE).
### Compte rendu (sk-protocol § 3)
[sk-protocol] Skills dans le contexte : sk-protocol, ue-cpp-build, lp-commandlets, lp-paths.
1. Résultat : partiel. 7 clés sur 9 ont une cause confirmée, une preuve et un correctif ; K5 et K6 restent indécises (nœud non localisé, faute de dump).
2. Preuves : dumps `StreetMap_Static__SearchByAmenitySafe`, `StreetMap_Static__EventGraph`, `Room__pickPresetWorld`, `GI__clearVariable`, `GI__EventGraph`, `Person__standIdle` ; `Src\LPMapMarkersGuardSubsystem.cpp/.h` ; moteur 5.8 (`ScriptCore.cpp`, `KismetArrayLibrary.cpp`, `UObjectGlobals.cpp/.h`, `PrimitiveComponent.cpp`) et 5.5 (`UObjectGlobals.h:972`).
3. Chiffres : Grep `count` par clé sur CIBLE et REF (§ Constats, recompte), tous égaux à STA-T1.
4. Incertitudes : K1 forme nue ; K5 et K6 ; noms des `CustomEvent` (estimés).
5. Écarts : budget dépassé (≈ 58 appels d'outils contre ≈ 40 prévus, pic non mesuré) ; `MapMarkers_C` non dumpé, donc lu au journal et dans `Source\`. Fichier écrit : `D:\Game\LifePlay-master 5.5\Docs\Migration\etat\ST\STA\STA-T2b-VILLE-DIAG.md` (seul).
6. Suite : l'orchestrateur doit lancer un N0 (dumps `GI` candidats pour K5, `SexInterface` pour K6), trancher Q1 et Q2, puis passer K1-K4, K7-K9 à STA-T2c. État : diagnostic livré, aucune correction faite.