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

145 lines
18 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-GEL1 — Niveau N3 — Modèle prévu (modèle de l'agent lp-diag) | utilisé claude-opus-5-5 — Agent lp-diag — Skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths
# STE-GEL1-DIAG : gel de 34 s pendant la recherche « sur tous les bâtiments » (2026-10-14)
Lecture seule. Pas d'éditeur, de MCP, de commandlet, de build ni de jeu. L'exécutable du build de l'auteur n'a pas été lancé. Les dumps manquants sont donnés comme commandes à lancer (§ 5.3), sans avoir été lancés.
## 1. Symptôme et reproduction
### 1.1 Journal (§ journal)
- Fichier : `D:\Game\LifePlay_Backup_5.5\COMMUN-5\essais\LifePlay_commun5_session1_BOSS.log`, MD5 `9c1ebcdc7fa87526f9070832f3c51f25`, 75 384 lignes (`md5sum`, `wc -l`).
- Séquence relevée (n° de ligne du journal, puis `[heure][image]`) :
- l. 1828 `[22.00.56:881][133]` `LogPlayerController: Error: InputMode:UIOnly - Attempting to focus Non-Focusable widget SObjectWidget`. C'est l'ouverture du panneau de recherche : `SetInputMode_UIOnlyEx` avec `InWidgetToFocus = self` (TopRight) est appelé après `SearchSection.SetVisibility` (dump `ST\STE\dumps\TopRight__EventGraph_D9.txt` l. 909-916 et 174-177). Établi pour le nœud, estimé pour le rattachement au clic.
- l. 1830 `[22.00.58:267][214]` `[TopRight_C_2147482047] X=20411.688 Y=0.000 at least 2 result`.
- l. 1831 `[22.01.32:447][214]` `LPDebug hitch : 34229.3 ms (seuil 50.0) #12`.
- l. 1832 et suivantes `[22.01.32:457][215]` : la vidange périodique `PersonsDump` reprend, à l'image suivante.
- **Les lignes 1830 et 1831 portent la même image `[214]`.** Le gel est donc dans l'image qui imprime `at least 2 result`, après l'impression. Aucune ligne entre les deux : le thread de jeu est resté bloqué dans le même appel Blueprint.
- Avant le gel (l. 1789-1827) : rien d'anormal. On voit `CloseSpace`/`OpenSpace` (GI), puis des vidanges `PersonsDump` à `menus=[]`, `CurrentScene=[]` et `Persons=14`. Aucune action de scène n'est en cours.
- Ce que mesure `LPDebug hitch` : `ULPDebugSubsystem::OnEndFrame` (`Source\LifePlay\LPDebugSubsystem.cpp:664-688`) calcule `DeltaMs = (Now - LastFrameSeconds) * 1000` entre deux fins d'image (l. 676). Il journalise au-delà du seuil (l. 678-685) et pose un `TRACE_BOOKMARK` (l. 686). Le message ne donne ni pile ni nom de fonction : c'est la durée totale de l'image 214, journalisée en fin d'image.
### 1.2 Sens des valeurs imprimées (points amont)
- `X=20411.688 Y=0.000` est une **position**, pas un compteur. C'est la sortie `Vector2d` de `DetermineBirdviewLooking` (centre de la vue), convertie par `Conv_Vector2dToString` puis concaténée à `' at least 2 result'` (dump D9 l. 2830-2849 et 3365-3368). Établi.
- `at least 2 result` est un texte fixe, imprimé si `Array_Length(Results) > 1` (dump D9 l. 2850-2862 : `Greater_IntInt(B=1)` vers `IfThenElse_6.then` vers `DetermineBirdviewLooking` vers `PrintString`). Il ne donne pas le nombre de résultats. Établi.
### 1.3 Reproduction
Carte `CentralLondon` : vue aérienne, ouvrir la recherche, laisser le terme vide, garder le filtre « All » (`amen_all` met `AmenityFilter` à vide, dump D9 l. 203-218), puis valider. Pas rejoué ici (jeu interdit par la fiche).
## 2. Hypothèse testée (une seule)
H1 : le gel vient du tri `TopRight.SortByDistance`, appelé juste après l'impression sur **tous** les bâtiments de la ville (≈ 49 425). C'est un tri Blueprint de coût quadratique, de la forme « minimum du tableau répété n fois ». Il existait déjà en 4.27 et chez l'auteur ; ce n'est pas une régression des lots en cours.
## 3. Essai et résultat brut
### 3.1 Chemin (§ chemin), du `TextCommit` à l'opération coûteuse
Source : dump `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\TopRight__EventGraph_D9.txt`.
1. `TextCommit(Term)` (l. 841-844). La touche Entrée passe par `SwitchEnum_0.OnEnter` puis `TextCommit` (l. 845-849, 880-887).
2. `Array_Clear(Results)` (l. 2586-2590), puis `AmenitySection.SetVisibility(Hidden)` (l. 2489-2493), puis `SearchResultBox.ClearChildren` (l. 828-834).
3. `IsValid(GI.CurrentCityExpansion)` (l. 2572-2579) :
- si valide : `StreetMap::SearchByBuildingNameFromArray(Term, AmenityFilter, CurrentCityExpansion.Buildings)` (l. 2603-2612), puis `Array_Append` ;
- dans tous les cas : `Array_Append(Results, StreetMap::SearchByBuildingName(Term, AmenityFilter))` (l. 769-773, 2594-2599).
4. C++ `UStreetMap::SearchByBuildingName` (`Plugins\StreetMap\Source\StreetMapRuntime\StreetMap.cpp:74-98`) : si le terme est vide, **`Results = Buildings`**, soit la copie de tous les bâtiments de la ville (l. 87-90). Le filtre d'aménité ne joue que si `Amenity.Len() > 0` (l. 92-95). `SearchByBuildingNameFromArray` suit la même logique (l. 100-124). Ce code est en boucle linéaire native : il n'est pas la cause.
5. `Array_Length(Results) > 1` (l. 2850-2862), puis `DetermineBirdviewLooking` (l. 2845-2849), puis `PrintString` (l. 2834-2844, **ligne de journal 1830**).
6. **`SortByDistance(Buildings = Results, Player Position = vue)`** (l. 2615-2621), puis `Results = SortedBuildings` (l. 2622-2627). C'est la seule opération lourde placée entre l'impression et la fin de l'image.
7. `ForEachLoopWithBreak(Results)` (l. 774-781) avec une coupure à l'indice 1000 (`Less_IntInt(B=1000)`, l. 782-791). Dans la boucle : `CreateWidget` (`SearchResult_C`), `Building = élément`, `ResultText.SetText`, `SearchResultBox.AddChild` (l. 801-879). Au plus 1 000 widgets.
### 3.2 Taille de la boucle
- `CentralLondon` compte **49 425 bâtiments** (`D:\Game\LifePlay-master 5.8\Docs\Journal_Modifications.md:820`). Les 280 villes en comptent 53 050 au total (`Docs\Migration\etat\U58\U8\U8_LPCheckStreetMap_20261011-003558.txt:52`). Le monde courant est bien `CentralLondon` (journal l. 1794).
- Avec le terme vide et le filtre « All », `Results` contient donc environ 49 425 éléments, plus ceux de l'extension si elle est valide. Le nombre exact de l'image 214 n'est pas journalisé : estimé.
### 3.3 Contenu de `SortByDistance` (pas de dump : table de noms)
Commande :
```
grep -a -o -E "MinOfFloatArray|WhileLoop|SortByDistance|SortedBuildings|DistanceFloats|FindCenterOfPolygon|at least 2 result|SearchByBuildingNameFromArray|SearchByBuildingName|ForEachLoopWithBreak|SearchResult_C" <TopRight.uasset> | sort | uniq -c
```
| Nom | 5.8 `Content\UI\TopRight.uasset` (MD5 1fcb94a6…) | 4.27 `ARCH427\LifePlay-master 4.27\Content\UI\TopRight.uasset` (c34f7bb0…) | 2023 `D:\XxX\LifePlay_2023_04_Stable_64bit\LifePlay\Content\UI\TopRight.uasset` (db046e3b…) + `.uexp` |
|---|---|---|---|
| SortByDistance | 16 | 12 | 2 |
| SortedBuildings | 2 | 2 | 2 |
| DistanceFloats | 1 | 1 | 1 |
| MinOfFloatArray | 4 | 3 | 3 |
| WhileLoop | 1 | 1 | — (macro dépliée à la cuisson) |
| FindCenterOfPolygon | 3 | 3 | — |
| SearchByBuildingName / …FromArray | 2 / 2 | 2 / 2 | 2 / 2 |
| `at least 2 result` | 2 | 2 | 1 (`.uexp`) |
- Estimé : `SortByDistance` calcule un tableau `DistanceFloats` (centre `FindCenterOfPolygon` puis distance), puis tourne dans une `WhileLoop`. À chaque tour, `MinOfFloatArray` parcourt tout `DistanceFloats`, ajoute le bâtiment le plus proche à `SortedBuildings` et neutralise sa distance (`Array_Set`). C'est un tri par sélection : n tours de n lectures, soit ≈ 49 425² ≈ 2,4 × 10⁹ comparaisons. Il faut y ajouter, à chaque tour, les copies de structures `FStreetMapBuilding` (tableau `BuildingPoints` compris) faites par `Array_Get`/`Array_Add` en machine virtuelle Blueprint.
- Surcoût propre à UE 5 (estimé, mécanisme établi dans le moteur) :
- `MinOfFloatArray` prend un `const TArray<float>&` (`UE_5.8\Engine\Source\Runtime\Engine\Classes\Kismet\KismetMathLibrary.h:764`) ;
- un tableau Blueprint « Float » est en double depuis UE 5 ;
- le compilateur Kismet insère alors une conversion de conteneur, `ArrayConversionFunction`, pour un tableau double passé à un paramètre float (`UE_5.8\Engine\Source\Editor\KismetCompiler\Private\KismetCastingUtils.cpp:240-244`) ;
- chaque appel recopie donc les ≈ 49 000 valeurs avant de les parcourir ;
- ce n'est pas vrai en 4.27, où tout est en float ;
- le nœud pur peut aussi être réévalué une fois par nœud impur qui le consomme.
- `MaximumLoopIterationCount=100000000` (`Config\DefaultEngine.ini:51`) : la `WhileLoop` (≈ 49 000 tours) ne déclenche pas la garde de boucle infinie. Le gel va donc à son terme sans erreur, ce qui colle avec l'absence de ligne pendant le gel.
### 3.4 Répétabilité (autres journaux)
Commandes : Grep `at least 2 result` avec `-A 1` sur `D:\Game\LifePlay_Backup_5.5\**\*.log` (80 occurrences, 21 fichiers), puis `grep -r -A1 --include=*.log "at least 2 result" LifePlay_Build_5.8_* LifePlay_Build_5.5_* D:/XxX/LifePlay_2023_04_Stable_64bit`.
| Journal (ligne) | Moteur | Impression → ligne suivante | Même image ? | Lecture |
|---|---|---|---|---|
| COMMUN-5 `LifePlay_commun5_session1_BOSS.log:1830-1831` | 5.8 | hitch **34 229,3 ms** | oui `[214]` | gel |
| COMMUN-3 `LifePlay_commun3_session1_definitif_BOSS.log:29019-29020` | 5.8 | hitch **35 696,9 ms** | oui `[664]` | gel, même motif |
| COMMUN-3 idem l. 29403-29404 (même vue, 17 s plus tard) | 5.8 | hitch 357,4 ms | oui `[649]` | recherche filtrée probable (terme inconnu) |
| `builds_supprimees\LifePlay_Build_5.5_L5d\Logs\LifePlay-backup-2026.09.29-22.53.35.log:85900-85901` | **5.5** | 22.23.59:660 → 22.24.40:554 = **40,9 s** | oui `[371]` | gel déjà présent en 5.5 (2026-09-29), avant le hitch logger |
| `LifePlay_Build_5.8_Development\…\LifePlay-backup-2026.10.08-20.53.33.log` (copie `etat\U58\U6\LifePlay_58_session2.log:8331`) | 5.8 | 20.50.52:676 → 20.51.30:755 = 38,1 s | non (`[619]` → `[9]`) | incertain (images écoulées) |
| CAM-B2b, COMMUN-1/2, O4, V1rc, L7a/b/i, L6a/e, L7cdj, STE-J5, CAM-B2 | 5.5 / 5.8 | 0,01 à 2,5 s (15,8 s pour V1rc l. 1204021, images différentes) | souvent non | recherches courtes (terme ou filtre), l'image n'est pas bloquée |
- Le motif se répète : trois gels de 34 à 41 s dans la même image, deux en 5.8 et un en 5.5. Toutes les autres recherches journalisées durent ≈ 1 s ou moins.
- Aucun journal ne donne le terme ni le nombre de résultats : la corrélation « terme vide et filtre All » reste estimée. Elle colle avec l'énoncé de l'utilisateur et avec le coût en n² : (6 635 / 49 425)² × 35 s ≈ 0,6 s pour les 6 635 bâtiments qui ont une aménité (`U8_LPCheckStreetMap…txt:53`).
### 3.5 4.27 et build de l'auteur 2023 (§ 4.27 / 2023)
- 4.27 : même chaîne. La table de noms est identique (§ 3.3). Le cache Blueprint de Visual Studio `ARCH427\LifePlay-master 4.27\.vs\LifePlay\BlueprintCache\LifePlay.json` cite `/Game/UI/TopRight.TopRight_C:SortByDistance`, `DetermineBirdviewLooking` et `StreetMap:SearchByBuildingNameFromArray` dans le graphe de TopRight (`grep -a -o`). Aucun journal 4.27 ne contient la ligne `at least 2 result` (Grep sur `D:\Game\LifePlay_Backup_5.5`, aucun chemin `ARCH427`) : la durée en 4.27 n'est pas mesurée.
- 2023 (cuit UE 4.20) : mêmes noms et même texte `at least 2 result` (`TopRight.uexp`). Aucun journal dans le dossier. Durée non mesurée.
- Conclusion : le chemin et l'algorithme viennent de l'auteur. En UE 5, la conversion double→float de `MinOfFloatArray` alourdit probablement chaque tour (estimé, non chiffré).
### 3.6 Changements non commités du build COMMUN-5 : hors du chemin
- `git status --porcelain -- Content/UI/TopRight.uasset Plugins/StreetMap` ne renvoie rien : ni TopRight ni StreetMap ne sont modifiés. Dernier commit de TopRight : `f32df0d` (STE-H10, événement `Current`, hors recherche). `StreetMap.cpp` n'a pas bougé depuis `1122e17` (L0). HEAD est `0745917`.
- Sur le chemin, GI ne fournit que des lectures de variables (`CurrentCityExpansion`, `Birdview`, `PC` ; dump D9 l. 2569-2574, 2639-2643, 2803-2805). Person et Room n'y sont pas. Les changements G16, B7-a, STG et CAM ne sont donc pas sur le chemin (établi pour le graphe, et pour les fichiers TopRight et StreetMap).
## 4. Verdict
**H1 confirmée pour le lieu, estimée pour le mécanisme interne.**
- Établi : le gel se produit dans l'image de `TextCommit`, après l'impression (même image `[214]`). La seule opération non bornée entre l'impression et la fin de l'image est `TopRight.SortByDistance`, appelée sur `Results` = tous les bâtiments (≈ 49 425 avec le terme vide et le filtre All : `StreetMap.cpp:87-90`). La boucle de widgets qui suit est bornée à 1 000.
- Estimé : `SortByDistance` est un tri par sélection Blueprint en O(n²) (`WhileLoop` + `MinOfFloatArray` + `Array_Set`, d'après la table de noms). En UE 5, une conversion de tableau s'y ajoute à chaque appel. Le gel était déjà là en 5.5 (40,9 s, L5d), et l'algorithme vient de l'auteur (4.27, 2023). Ce n'est pas une régression des lots.
- Pour le passer en « établi » : le dump de `SortByDistance` (§ 5.3, D1) et une trace Insights de l'image (D3).
### 4.1 Correctif proposé et estimation (§ correctif)
| Option | Contenu | Fichiers | Taille | Risque | Test |
|---|---|---|---|---|---|
| **C1 (recommandée)** : tri natif | `ULPDataCompat::SortBuildingsByDistance(const TArray<FStreetMapBuilding>&, FVector2D, int32 MaxCount)` en C++ : centres calculés une fois (même formule que `ULPDataCompat::FindCenterOfPolygon`, `Source\LifePlay\LPDataCompat.h:32-38`), tri `Algo::Sort` ou tri partiel des `MaxCount` premiers. Rebranchement par MCP de **1 nœud** (`K2Node_CallFunction_132`, dump D9 l. 2615). Les 4 appels depuis `Person` (`recommend*`, `STE-H8-DIAG.md:152`) sont optionnels | `Source\LifePlay\LPDataCompat.{h,cpp}`, `Content\UI\TopRight.uasset` | ≈ 40 lignes C++, 1 nœud | moyen : l'équivalence du tri (métrique, égalités, centre) doit être prouvée par le dump D1 ; il faut recompiler | rejouer la recherche vide en jeu, hitch < 1,5 s attendu (estimé) ; ordre identique sur une recherche filtrée (comparer les 20 premiers noms avant/après) |
| C2 : garde Blueprint | Avant `SortByDistance`, `Branch(Array_Length(Results) > 2000)` : au-delà, pas de tri (ou tri des seuls 1 000 premiers résultats). Variante : refuser la recherche si le terme est vide et le filtre « All » (message) | `Content\UI\TopRight.uasset` (MCP) | 3-4 nœuds | faible ; l'ergonomie change (liste non triée ou recherche refusée) | même rejeu ; la recherche filtrée reste triée |
| C3 : découpage sur plusieurs images | `SortByDistance` réécrit en tranches par minuterie, avec un indicateur « recherche… » | TopRight | gros graphe | élevé | — |
Estimation :
- C1 : ½ à 1 jour avec D1, compilation, MCP (instantané d'abord), build et VAL en jeu.
- C2 : 1 à 2 h plus la VAL.
- Gain attendu de C1 (estimé) : de 34 s à < 1,5 s. Le résidu se compose des copies de `SearchByBuildingName`, de 49 000 centres natifs et de 1 000 widgets. Pour comparaison, 49 425 appels au `FindCenterOfPolygon` Blueprint coûtaient 0,742 s (`Docs\18_Build_Debug.md:234`).
### 4.2 Faisable avant l'alpha (2026-10-18) ? (§ avant l'alpha)
- **C2 : oui.** Petit, réversible, sans C++, une seule passe MCP plus le build COMMUN suivant.
- **C1 : oui sous condition.** Le dump D1 doit être obtenu tôt (14-15 octobre) et l'équivalence du tri prouvée. Sinon, livrer C2 pour l'alpha et C1 ensuite.
- Recommandation : C2 pour l'alpha (garde au-delà de 2 000 résultats), C1 après. La décision revient à l'utilisateur.
## 5. Hypothèse suivante proposée, et à quel niveau
### 5.1 Hypothèse suivante
H2 (N2, lecture) : le corps exact de `SortByDistance` est un tri par sélection `WhileLoop`/`MinOfFloatArray`, avec un nœud pur réévalué par chaque consommateur. Elle se tranche par le dump D1 seul.
### 5.2 Questions
- Q1 (utilisateur) : pour l'alpha, une liste non triée au-delà de 2 000 résultats est-elle acceptable, ou faut-il plutôt refuser une recherche vide sur « All » (C2, variante) ?
- Q2 (orchestrateur) : C1 doit-il aussi rebrancher les 4 appels de `Person.recommend*` à `TopRight.SortByDistance` (tri des recommandations, `STE-H8-DIAG.md:152`) ? Ils sont aujourd'hui sur de petits tableaux (O9 `b_gt_stats.csv:5186` : 3 appels, 7,6 ms).
- Q3 : la valeur `X=20411.688` (5.8) contre `X=411.688` (5.5) pour la même vue par défaut (L7b, V1rc, L6e) est-elle un décalage voulu (lot CAM) ? Elle est sans effet sur le gel, mais déplace le centre du tri.
### 5.3 Lectures et dumps à faire lancer (non lancés ici)
- D1 : `.\Tools\RunCommandlet.ps1 -Lot STE-GEL1 -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=SortByDistance -Pins -Exec'` (forme reprise de `STE-H8-DIAG.md:502`).
- D2 : la même commande avec `-Fn=DetermineBirdviewLooking`.
- D3 : une trace Insights (`-trace=cpu,frame,bookmark`) pendant une recherche vide sur « All ». Le signet `LP Hitch` (`LPDebugSubsystem.cpp:686`) situe l'image ; il faut lire le temps exclusif de `SortByDistance`, `MinOfFloatArray` et `Array_Get`.
- D4 : en jeu, compter `Results` (impression temporaire de `Array_Length` ou point d'arrêt) pour établir n.
Tours : 29 / 45
## Constats
- `X=… Y=…` est le centre de la vue, pas un compteur ; `at least 2 result` est un texte fixe dès que `Results > 1`.
- La ligne qui précède le hitch et le hitch partagent la même image : un gel « sans ligne » s'attribue à l'appel en cours dans cette image, ici le tri qui suit `PrintString`.
- Piège moteur UE 5 : un tableau Blueprint « Float » (double) passé à `MinOfFloatArray(const TArray<float>&)` est converti en entier à chaque appel (`KismetCastingUtils.cpp:240-244`). Dans une boucle, cela multiplie le coût d'un algorithme déjà en n².
- Le build de l'auteur et la copie 4.27 ont le même chemin. Aucun journal 4.27 ou 2023 ne permet de chiffrer leur durée.
- Fichier écrit : `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-GEL1-DIAG.md` (A, à commiter par le gardien).
## Décision de l'utilisateur (2026-10-14, session STE)
- Correctif retenu : **C2, garde Blueprint dans TopRight** : pas de tri par distance au-delà de 2 000 résultats (la recherche vide sur « All » reste permise) ; visé pour l'alpha ; SPEC après le dump D1 (`TopRight.SortByDistance`, UE-DEMANDE à Big Boss) ; IMPL sous instantané et UE-GO. Q2 (appels de Person) et Q3 (décalage X=20411) : hors C2, non tranchées.