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

18 KiB
Raw Blame History

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.