145 lines
18 KiB
Markdown
145 lines
18 KiB
Markdown
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.
|