18 KiB
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, MD59c1ebcdc7fa87526f9070832f3c51f25, 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_UIOnlyExavecInWidgetToFocus = self(TopRight) est appelé aprèsSearchSection.SetVisibility(dumpST\STE\dumps\TopRight__EventGraph_D9.txtl. 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ériodiquePersonsDumpreprend, à l'image suivante.
- l. 1828
- Les lignes 1830 et 1831 portent la même image
[214]. Le gel est donc dans l'image qui imprimeat 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 vidangesPersonsDumpàmenus=[],CurrentScene=[]etPersons=14. Aucune action de scène n'est en cours. - Ce que mesure
LPDebug hitch:ULPDebugSubsystem::OnEndFrame(Source\LifePlay\LPDebugSubsystem.cpp:664-688) calculeDeltaMs = (Now - LastFrameSeconds) * 1000entre deux fins d'image (l. 676). Il journalise au-delà du seuil (l. 678-685) et pose unTRACE_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.000est une position, pas un compteur. C'est la sortieVector2ddeDetermineBirdviewLooking(centre de la vue), convertie parConv_Vector2dToStringpuis concaténée à' at least 2 result'(dump D9 l. 2830-2849 et 3365-3368). Établi.at least 2 resultest un texte fixe, imprimé siArray_Length(Results) > 1(dump D9 l. 2850-2862 :Greater_IntInt(B=1)versIfThenElse_6.thenversDetermineBirdviewLookingversPrintString). 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.
TextCommit(Term)(l. 841-844). La touche Entrée passe parSwitchEnum_0.OnEnterpuisTextCommit(l. 845-849, 880-887).Array_Clear(Results)(l. 2586-2590), puisAmenitySection.SetVisibility(Hidden)(l. 2489-2493), puisSearchResultBox.ClearChildren(l. 828-834).IsValid(GI.CurrentCityExpansion)(l. 2572-2579) :- si valide :
StreetMap::SearchByBuildingNameFromArray(Term, AmenityFilter, CurrentCityExpansion.Buildings)(l. 2603-2612), puisArray_Append; - dans tous les cas :
Array_Append(Results, StreetMap::SearchByBuildingName(Term, AmenityFilter))(l. 769-773, 2594-2599).
- si valide :
- 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 siAmenity.Len() > 0(l. 92-95).SearchByBuildingNameFromArraysuit la même logique (l. 100-124). Ce code est en boucle linéaire native : il n'est pas la cause. Array_Length(Results) > 1(l. 2850-2862), puisDetermineBirdviewLooking(l. 2845-2849), puisPrintString(l. 2834-2844, ligne de journal 1830).SortByDistance(Buildings = Results, Player Position = vue)(l. 2615-2621), puisResults = SortedBuildings(l. 2622-2627). C'est la seule opération lourde placée entre l'impression et la fin de l'image.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
CentralLondoncompte 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 bienCentralLondon(journal l. 1794).- Avec le terme vide et le filtre « All »,
Resultscontient 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é :
SortByDistancecalcule un tableauDistanceFloats(centreFindCenterOfPolygonpuis distance), puis tourne dans uneWhileLoop. À chaque tour,MinOfFloatArrayparcourt toutDistanceFloats, ajoute le bâtiment le plus proche àSortedBuildingset 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 structuresFStreetMapBuilding(tableauBuildingPointscompris) faites parArray_Get/Array_Adden machine virtuelle Blueprint. - Surcoût propre à UE 5 (estimé, mécanisme établi dans le moteur) :
MinOfFloatArrayprend unconst 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) : laWhileLoop(≈ 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.jsoncite/Game/UI/TopRight.TopRight_C:SortByDistance,DetermineBirdviewLookingetStreetMap:SearchByBuildingNameFromArraydans le graphe de TopRight (grep -a -o). Aucun journal 4.27 ne contient la ligneat least 2 result(Grep surD:\Game\LifePlay_Backup_5.5, aucun cheminARCH427) : 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
MinOfFloatArrayalourdit 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/StreetMapne renvoie rien : ni TopRight ni StreetMap ne sont modifiés. Dernier commit de TopRight :f32df0d(STE-H10, événementCurrent, hors recherche).StreetMap.cppn'a pas bougé depuis1122e17(L0). HEAD est0745917.- 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 estTopRight.SortByDistance, appelée surResults= 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é :
SortByDistanceest 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 auFindCenterOfPolygonBlueprint 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 (O9b_gt_stats.csv:5186: 3 appels, 7,6 ms). - Q3 : la valeur
X=20411.688(5.8) contreX=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 deSTE-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 signetLP Hitch(LPDebugSubsystem.cpp:686) situe l'image ; il faut lire le temps exclusif deSortByDistance,MinOfFloatArrayetArray_Get. - D4 : en jeu, compter
Results(impression temporaire deArray_Lengthou 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 resultest un texte fixe dès queResults > 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.