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

38 KiB
Raw Blame History

Fiche STE-GEL1-S — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-sonnet-5-5 — Agent lp-cart — Skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths (préchargés), lp-mcp (lu : § 1-4), lp-doc (gabarits)

STE-GEL1-SPEC : garde C2 dans TopRight, pas de tri par distance au-delà de 2 000 résultats (2026-10-14)

Lecture seule stricte (Read, Grep, Glob ; quelques Bash en lecture : git, md5sum, grep -a sur binaires, ls). Aucun éditeur, MCP, commandlet, build ni jeu. Copie D:\Game\LifePlay-master 5.8, branche ue5.8, HEAD 0745917. Gabarit SPEC : D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md:36-38 (sections 1 à 7 ci-dessous). Correspondance avec la rubrique 7 de la fiche : objectif = § 1 ; corps de SortByDistance = § 1.1 ; fichiers et ordre = § 2 ; préalables = § 2.0 ; garde au geste près = § 2.2 ; comportement = § 2.3 ; repli = § 3 ; risques et Q# = § 4 ; interdits = § 5 ; critères V5 / V6 = § 7.

Statut : complet et exécutable (les deux dumps D1 sont lus ; aucune donnée manquante pour la variante principale). Trois points à lire avant l'IMPL :

  1. Le n² est établi sur D1 (structure du graphe, § 1.1) ; le chiffrage en secondes reste estimé (pas de trace Insights).
  2. Au-delà de 2 000 résultats, les 1 000 widgets affichés sont les 1 000 premiers de Results dans l'ordre d'import des bâtiments, pas les 1 000 plus proches (§ 1.4, § 2.3).
  3. SortByDistance existe en deux exemplaires : celui de TopRight (cible) et un homonyme défini dans Content\BPs\StreetMap_Static.uasset (§ 1.2) ; hors C2.

Abréviations (chemins absolus) :

  • D = D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\ ; SBD = DTopRight__SortByDistance_D1.txt (29 nœuds ; listing 1 = l. 1-129, listing 2 d'exécution = l. 130-179) ; EG = DTopRight__EventGraph_D1.txt (907 nœuds ; listing 1 = l. 1-4188, second listing dès l. 4189, non lu).
  • DIAG = STE-GEL1-DIAG.md, D1R = STE-GEL1-D1.md (dans etat\ST\STE\) ; UE = D:\Game\Launcher\Engine\UE_5.8\Engine\Source\ (moteur installé, lu) ; LD = Source\LifePlay\LPDebugSubsystem.cpp (arbre de travail, M à l'état initial) ; SM = Plugins\StreetMap\Source\StreetMapRuntime\StreetMap.cpp.
  • J5 = D:\Game\LifePlay_Backup_5.5\COMMUN-5\essais\LifePlay_commun5_session1_BOSS.log.

État relevé (Bash, lecture) : git rev-parse --short HEAD → 0745917 ; md5sum Content/UI/TopRight.uasset → 1fcb94a60061b418e9deeb08bc1a990d (= 1FCB94A6… des dumps, D1R:28 et 43) ; git status --short -- Content/UI/TopRight.uasset Plugins/StreetMap → aucune ligne (non modifiés) ; git log --oneline -1 -- Content/UI/TopRight.uasset → f32df0d (STE-H10). Les numéros de ligne du DIAG (« dump D9 périmé ») sont refaits sur D1 : PrintString l. 2832 (D9 : 2834), appel SortByDistance l. 2613 (2615), boucle de widgets l. 772-792 (774-791).

1. Objectif mesurable

Insérer dans TopRight:EventGraph une Branche entre le PrintString « at least 2 result » et l'appel de SortByDistance : si Array_Length(Results) <= 2000, l'exécution suit le chemin actuel (tri puis boucle de widgets) ; sinon elle saute le tri et va directement à la boucle de widgets (MacroInstance_1, ForEachLoopWithBreak), Results restant tel que rendu par la recherche. Décision de l'utilisateur du 2026-10-14 (DIAG, « Décision de l'utilisateur »). La recherche vide sur « All » reste permise.

Attendus mesurables (§ 7) : TopRight:EventGraph 907 → 909 nœuds (2 créés), 1 coupure, 5 liens, 1 set_pin_value ; SortByDistance inchangé (29 nœuds, mêmes liens) ; LPTestStringCompat 3120 / 0 / 60 ; en jeu (V6), la recherche vide sur « All » à CentralLondon ne produit plus de LPDebug hitch de plusieurs dizaines de secondes (avant : 34 229,3 ms, J5:1830-1831 relu) mais moins de 2 000 ms, et une recherche ciblée reste triée par distance.

1.1 Corps de SortByDistance (SBD, 29 nœuds) : algorithme, coût, n² confirmé

Entrées / sorties : FunctionEntry_1551 → Buildings (tableau de StreetMapBuilding) et Player Position (Vector2D) (SBD:2-5) ; FunctionResult_0 → SortedBuildings (SBD:100-102). Variables locales : FirstPoints, DistanceFloats, IndicesOrdered, BuildingsSorted (SBD:26-27, 33-34, 42-43, 55-56, 62-63, 83-86, 98-99).

Phase Nœuds (SBD) Ce qu'elle fait Coût
A MacroInstance_23 ForEachLoop sur Buildings (6-12) ; BreakStruct_24 (13-18) ; CallFunction_5637 GI_C::FindCenterOfPolygon(BuildingPoints) (106-112) ; CallArrayFunction_46 Array_Add(FirstPoints, Center) (19-25) un centre par bâtiment n tours, linéaire. Ordre de grandeur : 49 425 appels de FindCenterOfPolygon = 0,742 s inclus (Docs\18_Build_Debug.md:234, relu ; contexte City, transposé ici : estimé)
B MacroInstance_31 ForEachLoop sur FirstPoints (35-41) ; CallFunction_695 Subtract_Vector2DVector2D(centre, Player Position) (28-32) ; CallFunction_1025 VSize2D (44-47) ; CallArrayFunction_89 Array_Add(DistanceFloats) (48-54) une distance par bâtiment n tours, linéaire
C MacroInstance_51 WhileLoop (57-61), Condition ← CallFunction_6094 Less_DoubleDouble(MinValue, constante ≈ 10⁶³) (123-127) ; corps : CallArrayFunction_121 Array_Add(IndicesOrdered, IndexOfMinValue) (64-70) puis CallArrayFunction_308 Array_Set(DistanceFloats, IndexOfMinValue, constante ≈ 10⁶³, bSizeToFit=false) (115-122) ; CallFunction_1460 MinOfFloatArray(DistanceFloats) (71-75) tri par sélection : à chaque tour, minimum du tableau entier, indice ajouté à IndicesOrdered, entrée neutralisée n tours × ≥ 1 passe sur n valeurs = Θ(n²)
D MacroInstance_70 ForEachLoop sur IndicesOrdered (76-82) ; GetArrayItem_119(Buildings, indice) (94-97) ; CallArrayFunction_151 Array_Add(BuildingsSorted) (87-93) ; FunctionResult_0 recopie des bâtiments dans l'ordre n tours, linéaire (copie de structure avec tableau de points)

Verdict n² : établi (structure) :

  • La phase C neutralise exactement une entrée par tour (Array_Set à l'indice du minimum, SBD:115-122) et s'arrête quand le minimum atteint la constante (SBD:123-127) : n tours pour n bâtiments.
  • Chaque tour lit tout DistanceFloats (VariableGet_120 → CallFunction_1460.FloatArray, SBD:42-43, 73) : MinOfFloatArray appelle FMath::Min(FloatArray, &IndexOfMinValue) (UE\Runtime\Engine\Private\KismetMathLibrary.cpp:406-409), boucle for v = 1 … Num-1 (UE\Runtime\Core\Public\GenericPlatform\GenericPlatformMath.h:1026-1054, boucle l. 1039-1047). Soit n × n lectures : 49 425² ≈ 2,44 × 10⁹.
  • Estimé (mécanisme moteur lu, durée non mesurée) : (i) MinOfFloatArray prend const TArray<float>& (KismetMathLibrary.h:764) alors que le tableau Blueprint est en double : le compilateur insère une conversion de conteneur double → float (UE\Editor\KismetCompiler\Private\KismetCastingUtils.cpp:240-244, FloatingPointCastType::Container), donc une recopie des n valeurs à chaque appel ; (ii) CallFunction_1460 est un nœud pur à trois consommateurs (CallFunction_6094.A ← MinValue, CallArrayFunction_121.NewItem et CallArrayFunction_308.Index ← IndexOfMinValue, SBD:74-75) : le compilateur Kismet incorpore le code d'un nœud pur à chaque nœud qui en consomme la sortie (UE\Editor\KismetCompiler\Private\KismetCompiler.cpp:2881-2897), soit 2 à 3 évaluations complètes par tour (la part de la Condition de la macro WhileLoop n'est pas lue). Cohérent avec 34,2 s / 2,44 × 10⁹ ≈ 14 ns par élément et par tour. D3 (Insights) non fait.
  • Extrapolation quadratique (estimée, linéaire négligé) du tri conservé à la borne : n = 2 000 → (2 000 / 49 425)² × 34,2 s ≈ 0,056 s.
  • Tableau vide (hors C2, estimé) : FMath::Min d'un tableau vide rend INDEX_NONE et T() (GenericPlatformMath.h:1028-1035), donc MinValue = 0 < constante : la WhileLoop ne se termine pas avant MaximumLoopIterationCount=100000000 (Config\DefaultEngine.ini:51). La recherche TopRight est protégée par Array_Length > 1 (EG:2848-2861) ; les autres appelants (§ 1.2) ne sont pas lus.

1.2 Autres appelants de SortByDistance (hors C2, à lister)

Où Preuve Statut
TopRight:EventGraph, CallFunction_132 EG:2613-2619 ; Grep SortByDistance sur EG (listing 1) → 1 seule ligne (2613) appel visé par C2
Person (4 appels TopRight_C::SortByDistance) dump 5.5 Docs\Migration\etat\portage55\L6f\L6f_dump_Person.txt l. 9842, 9979, 10167, 10519 (self ← TopRight, Buildings ← résultat de recherche) ; SortedBuildings (vide) aux l. 9848, 9985, 10173, 10525 (sortie non reliée dans ce dump ancien : estimé « résultat jeté », à relire) ; Content\BPs\Person.uasset : grep -a -o → 1 occurrence du nom (5.8) nombre d'appels en 5.8 non établi (dump 5.5 antérieur à ee2821a, STE-H8-DIAG.md:144-145) ; Q2 du DIAG non tranchée ; tableaux petits (O9 : 3 appels, 7,6 ms, DIAG § 5.2)
StreetMap_Static Content\BPs\StreetMap_Static.uasset : grep -a -o -E → 12 × SortByDistance, 1 × SortByDistance.K2Node_FunctionEntry_9, 1 × SortByDistance.K2Node_FunctionResult_0 fonction homonyme définie dans ce Blueprint (entrée et résultat de fonction), non un appel ; contenu non dumpé ; ses appelants sont inconnus. Hors chemin de la recherche
Autres graphes de TopRight le nom figure 16 fois dans la table de noms de TopRight.uasset (`grep -a -o -F … wc -l` → 16, = DIAG § 3.3) : ne dit pas combien d'appels
Reste du projet grep -rlaF "SortByDistance" sur Content\BPs, Content\UI → Person, StreetMap_Static, TopRight seuls ; sur Content\Scenes, Structs, Developers, Collections, Render, Characters → aucun fichier ; Grep Content\Modules → 0 ; Grep Source → 0 ; Grep Plugins (*.cpp,h,ini,py,json,md) → 0 Content\Maps n'existe pas (message grep) ; non scannés : Heavy, AnimPacks, FreeAnimationsPack, Movies, Quaternius, Retarget_ue5, Saves, __External*__ (assets non Blueprint)

1.3 Chemin de la recherche dans l'EventGraph (EG, listing 1)

Nœud Rôle Liens (EG)
CustomEvent_17 TextCommit entrée then → CallArrayFunction_10 (Array_Clear Results) (839-842, 2584-2588) → … CallFunction_1570 (SearchResultBox.ClearChildren, 829-832) → MacroInstance_0 (IsValid de CurrentCityExpansion, 2573-2577)
VariableSet_7 / CallFunction_134 si l'extension est valide : Results = SearchByBuildingNameFromArray(...) 2601-2612
CallArrayFunction_17 Array_Append Results += SearchByBuildingName(Term, AmenityFilter) (CallFunction_804, 767-771) then → IfThenElse_6.execute (2592-2597)
IfThenElse_6 Array_Length(Results) > 1 : CallArrayFunction_11 (2848-2851) → CallFunction_139 Greater_IntInt(B=1) (2852-2856) then → CallFunction_10470 ; else → MacroInstance_1.Exec (2857-2861)
CallFunction_10470 DetermineBirdviewLooking impur ; sort Vector2d execute ← IfThenElse_6.then ; then → CallFunction_7736.execute ; Vector2d → CallFunction_7735.InVec et CallFunction_132.Player Position (2843-2847)
CallFunction_7736 PrintString texte « … at least 2 result » (EG:3366) then → CallFunction_132.execute (2832-2842, lien à couper)
CallFunction_132 SortByDistance cible then → VariableSet_26.execute ; Buildings ← VariableGet_546.Results ; Player Position ← CallFunction_10470.Vector2d ; SortedBuildings → VariableSet_26.Results (2613-2619)
VariableSet_26 Results = SortedBuildings then → MacroInstance_1.Exec (2620-2625)
MacroInstance_1 ForEachLoopWithBreak Exec ← VariableSet_26.then, IfThenElse_6.else ; Array ← VariableGet_547.Results ; Array Index → CallFunction_1338.A ; Break ← Knot_0 772-779
CallFunction_1338 / IfThenElse_76 / Knot_0 Less_IntInt(Array Index, 1000) ; then → CreateWidget_0 ; else → Knot_0 → Break 780-792 : au plus 1 000 widgets (indices 0 à 999)
VariableGet_546 Results 3 consommateurs : Array_Append_17.TargetArray, CallFunction_132.Buildings, CallArrayFunction_11.TargetArray 2589-2591

Results n'est lu ou écrit que par 5 nœuds (VariableGet_11, _546, _547, VariableSet_7, _26 : Grep variable 'Results' sur EG → l. 2581, 2589, 2598, 2607, 2620) : aucun autre consommateur ne dépend de l'ordre trié que la boucle d'affichage.

1.4 Quels 1 000 résultats s'affichent au-delà de 2 000 (point amont)

  • Ordre de Results : d'abord les résultats de CurrentCityExpansion.Buildings si l'extension est valide (VariableSet_7, EG:2601-2612), puis ceux de StreetMap.SearchByBuildingName ajoutés à la suite (Array_Append_17, EG:2592-2597). Établi par le flux.
  • Terme vide : Results = Buildings (copie dans l'ordre du tableau, SM:87-90) ; terme non vide : boucle sur Buildings dans l'ordre (SM:77-85) ; filtre d'aménité : parcourt la liste dans l'ordre (SM:133-139). Établi.
  • Ordre de Buildings : ajout au fil de l'import, une entrée par Way OSM de type bâtiment (Plugins\StreetMap\Source\StreetMapImporting\StreetMapFactory.cpp:547-560, new (StreetMapRef.Buildings) l. 321) : ordre des ways du fichier OSM (estimé : le remplissage de OSMFile.Ways n'est pas relu).
  • Donc, sans tri : les 1 000 premiers bâtiments de l'ordre d'import (ceux de l'extension d'abord) sont affichés, sans lien avec la vue ; avec tri, c'étaient les 1 000 plus proches. Un bâtiment sans nom produit un widget sans SetText dans les deux cas (IfThenElse_150, EG:858-871) ; la part de bâtiments sans nom parmi les 1 000 premiers n'est pas mesurée.

2. Fichiers touchés, ordre

Un seul fichier : Content\UI\TopRight.uasset, un seul graphe : EventGraph (refPath /Game/UI/TopRight.TopRight:EventGraph, format de STE-H10-SPEC.md:91, 487). Ni SortByDistance, ni Person, ni StreetMap_Static, ni GI, ni le C++, ni le plugin StreetMap. Pas de build pour l'IMPL (le build empaqueté de V6 relève de BUILD).

2.0 Préalables (dans l'ordre, avant tout create_node)

  • P0 : UE-GO de Big Boss (DIAG, « Décision de l'utilisateur » : IMPL sous instantané et UE-GO) ; gel annoncé à toutes les sessions (ListAgents, lp-mcp § 1.7) ; aucune autre IMPL sur TopRight en cours.
  • P1 (dumps) : D1 présents (SBD, EG). Refaire seulement si le MD5 de TopRight.uasset à l'entrée diffère de 1fcb94a60061b418e9deeb08bc1a990d : .\Tools\RunCommandlet.ps1 -Lot STE-GEL1 -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=EventGraph -Pins -Exec' -EtatDir "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE" (forme de D1R:11-13), éditeur fermé (.\Tools\UeState.ps1 -Kind Editeur = 0).
  • P2 : MD5 de TopRight.uasset relevé juste avant l'IMPL (attendu 1fcb94a6…) = valeur de repli.
  • P3 : .\Tools\Snapshot.ps1 -Lot STE-GEL1 -Path Content\UI\TopRight.uasset (paramètres -Lot, -Path : Tools\Snapshot.ps1:17-19) ; manifeste D:\Game\LifePlay_Backup_5.5\STE-GEL1\manifest.csv ; éditeur headless (lp-mcp § 2) ; AssetTools.load_asset /Game/UI/TopRight (rend /Game/UI/TopRight.TopRight).
  • P4 : get_node_infos {nodes:[{refPath:"/Game/UI/TopRight.TopRight:EventGraph.K2Node_<Nom>"}]} (dix nœuds au plus par appel) pour : CallFunction_7736, CallFunction_132, VariableSet_26, MacroInstance_1, IfThenElse_6, CallFunction_10470, CallArrayFunction_11, CallFunction_139, VariableGet_546. Liens = colonne « Avant » du § 2.2. Relever les indices (attendus, non relus : 7736.then sortie 0 ; 132.execute entrée 0 ; MacroInstance_1.Exec entrée 0 ; CallArrayFunction_11.ReturnValue sortie 0 ; VariableGet_546.Results sortie 0). Contrôler que 7736.then n'a qu'un lien (→ 132.execute), que 132.execute n'a qu'une source, que MacroInstance_1.Exec en a deux. Tout écart : arrêt et rapport.
    • P4b (graphes non dumpés) : find_nodes {graph:{refPath:"/Game/UI/TopRight.TopRight:<Graphe>"}, title:"SortByDistance"} sur les autres graphes de TopRight (liste : LPDumpFunction -BP=TopRight sans -Fn, D9 de STE-H8-DIAG.md:500) ; résultat noté, sans effet sur la garde.
  • P5 : avant chaque create_node, find_node_types {graph:{refPath:"/Game/UI/TopRight.TopRight:EventGraph"}, type_id_filter:<filtre>, context_pins:[]} ; le type_id attendu doit figurer dans la réponse :
    • Branche : filtre Branche → Utilitaires|ContrôleDeFlux|Branche (relu en 5.8 : STE-H3d-IMPL.md:69, STG-B7a-IMPL.md:28).
    • Comparateur <= entier : filtre gal, avec context_pins = la broche CallArrayFunction_11.ReturnValue ; réponse relue en STG-B7a-IMPL.md:24 : Utilitaires|Opérateurs|Égalà(==), …Inférieurouégalà(<=), …Supérieuràouégalà(>=) (préfixe Utilitaires|Opérateurs| relu en clair sur Inférieurà(<) et Égalà(==), STG-B7a-IMPL.md:25, 32) ; second essai : filtre Opérateurs|. Type attendu : Utilitaires|Opérateurs|Inférieurouégalà(<=) (suffixe relu seulement dans la liste abrégée : il doit être lu en clair ici). Absent : repli (b), § 3. Le filtre Less ne rend aucun comparateur (STG-B7a-IMPL.md:21).
    • Pas de nouvel Array_Length : CallArrayFunction_11 est réutilisé (si jamais nécessaire : Utilitaires|Tableau|Length, STE-H10-IMPL.md:49).
    • Après création : get_node_infos du nouveau nœud (indices relevés).
  • P6 : la valeur de seuil (2 000) et l'absence de message à l'utilisateur sont celles de la décision du 2026-10-14 (Q-GEL1-1 facultative).

2.1 Conventions (reprises de STE-H3g-SPEC.md § 2.1, lp-mcp § 3-4)

  • Cascade de Branche, jamais AND / OR / Select purs ; une sortie de données peut alimenter plusieurs entrées (les liens existants restent). delete_node interdit. Aucun FunctionResult créé.
  • Un opérateur promotable (Utilitaires|Opérateurs|…) se type par sa première liaison : relier avant set_pin_value (STG-B1-IMPL.md:193, STG-B7a-IMPL.md:44). Un seul set_pin_value ici (le seuil), contrairement à STE-H3g-SPEC.md:93.
  • Après la garde : compile_blueprint {blueprint:{refPath:"/Game/UI/TopRight.TopRight"}, warnings_as_errors:true} → null, journal de l'éditeur lu (aucune ligne Error / Warning liée à TopRight) ; puis save_assets {asset_paths:["/Game/UI/TopRight"]} ; fermeture de l'éditeur, UeState.ps1 = 0, MD5 relevé (hyper_blueprint_debug_inspect absent du serveur, STE-H3d-IMPL.md:156).
  • Positions (pos) libres, à côté de CallFunction_7736, sans chevauchement.

2.2 La garde au geste près (variante principale (a))

Emplacement : dans l'EventGraph, avant l'appel CallFunction_132, sur le lien CallFunction_7736.then → CallFunction_132.execute (EG:2834). Choix argumenté :

Dans l'EventGraph (retenu) En tête de SortByDistance
Nœuds 2 ≥ 3 et une sortie SortedBuildings à fournir sans tri : second nœud de retour ou variable locale (convention : aucun FunctionResult créé)
Appelants touchés la seule recherche TopRight (cible de C2) aussi les 4 appels Person (Q2 non tranchée), l'ordre de leurs listes pourrait changer
Réversibilité un lien rétabli fonction partagée à modifier

Nœuds créés (noms de la SPEC ; noms réels K2Node_* à noter dans l'IMPL) :

  • GEL-B : Branche Utilitaires|ContrôleDeFlux|Branche (entrées execute 0, Condition 1 ; sorties then 0, else 1 ; STE-H3d-IMPL.md:69).
  • GEL-OP : opérateur <= (type_id lu en P5) ; broches attendues A (entrée 0), B (entrée 1), ReturnValue (sortie 0), à relever avant câblage.

Séquence (un appel à la fois, relecture get_node_infos après chaque groupe) :

  1. create_node GEL-B. 2. create_node GEL-OP.
  2. connect_pins CallArrayFunction_11.ReturnValue (sortie 0) → GEL-OP.A : l'opérateur prend le type entier ; relire get_node_infos : A et B de type entier. Sinon arrêt (repli (b), § 3).
  3. set_pin_value GEL-OP.B = 2000 ; relire 2000.
  4. connect_pins GEL-OP.ReturnValue → GEL-B.Condition (1).
  5. break_pins CallFunction_7736.then / CallFunction_132.execute.
  6. connect_pins CallFunction_7736.then → GEL-B.execute (0).
  7. connect_pins GEL-B.then (0) → CallFunction_132.execute.
  8. connect_pins GEL-B.else (1) → MacroInstance_1.Exec. Volume : 2 nœuds créés, 1 break_pins, 5 connect_pins (3, 5, 7, 8, 9), 1 set_pin_value ; EventGraph 907 → 909 nœuds ; 1 lien perdu, 5 gagnés (net +4).
Broche Avant Après
CallFunction_7736.then → CallFunction_132.execute → GEL-B.execute (seul)
CallFunction_132.execute ← CallFunction_7736.then ← GEL-B.then (seul)
MacroInstance_1.Exec ← VariableSet_26.then, IfThenElse_6.else ← VariableSet_26.then, IfThenElse_6.else, GEL-B.else (3 sources)
CallArrayFunction_11.ReturnValue → CallFunction_139.A → CallFunction_139.A, GEL-OP.A
GEL-B.Condition / GEL-OP.B n'existent pas ← GEL-OP.ReturnValue / 2000

Inchangés à la relecture : CallFunction_132.Buildings ← VariableGet_546.Results, .Player Position ← CallFunction_10470.Vector2d, .then → VariableSet_26.execute ; VariableSet_26.then → MacroInstance_1.Exec ; IfThenElse_6 (then → CallFunction_10470, else → MacroInstance_1.Exec) ; CallFunction_139.B = 1 ; MacroInstance_1.Array ← VariableGet_547.Results ; CallFunction_1338.B = 1000 ; le PrintString et DetermineBirdviewLooking s'exécutent toujours (le marqueur « at least 2 result » du journal reste).

2.3 Comportement avant / après

Cas n = Array_Length(Results) Avant Après Changé
1 0 ou 1 IfThenElse_6.else → boucle (pas d'impression, pas de tri) idem non
2 2 à 2 000 impression, tri complet, boucle ≤ 1 000 widgets idem, plus une Branche vers then non (mêmes nœuds, mêmes entrées : identique par construction)
3 2 001 et plus impression, tri Θ(n²), 1 000 plus proches affichés impression, pas de tri, 1 000 premiers de Results (§ 1.4) oui
  • Nombre affiché : min(n, 1 000) dans les trois cas (CallFunction_1338.B = 1000, EG:780-784), inchangé.
  • Qui dépasse 2 000 : la recherche vide sur « All » à CentralLondon (49 425 bâtiments, Docs\Journal_Modifications.md:820, relu ; 49 425 appels de FindCenterOfPolygon, 18_Build_Debug.md:234). Les 279 autres villes totalisent 53 050 − 49 425 = 3 625 bâtiments (U8_LPCheckStreetMap_20261011-003558.txt:52, déduction : estimé), donc au plus une autre ville peut dépasser 2 000. Un filtre d'aménité garde au plus 5 191 bâtiments à Londres (Journal l. 820) et 6 635 au total (U8 txt:53) : un type très fréquent pourrait dépasser 2 000 (répartition par type non mesurée). Une recherche par terme court : non mesurée.
  • Coût résiduel pour n ≥ 2 001 : copies natives de SearchByBuildingName et Array_Append (n structures avec tableau de points), 1 000 CreateWidget / AddChild : non mesuré, attendu bien inférieur au gel (V6 : seuil 2 000 ms).
  • Aucun message ni indicateur n'est ajouté pour dire que la liste n'est pas triée (non demandé : Q-GEL1-1).

3. Repli

  • Avant écriture : instantané (P3), MD5 de départ (P2).
  • Aucun save_assets tant que compile_blueprint n'est pas à 0 diagnostic. Échec avant enregistrement : fermer l'éditeur sans enregistrer (le .uasset reste l'instantané), contrôler le MD5, rapporter. Aucun correctif improvisé.
  • Après enregistrement, lien relu différent, type_id introuvable, nœud inattendu : arrêt, rapport ; sur décision de l'orchestrateur, éditeur fermé : .\Tools\Restore.ps1 -Lot STE-GEL1 -Only Content\UI\TopRight.uasset -WhatIf (paramètres -Only, -WhatIf : Tools\Restore.ps1:18, 21), puis sans -WhatIf ; MD5 = valeur de P2. Un Restore défait toute autre écriture sur TopRight faite depuis l'instantané (IMPL jamais entrelacées).
  • Après commit (gardien) : git revert du commit du ticket STE-GEL1.
  • Repli de conception (b) — si Inférieurouégalà(<=) est absent de P5, si l'opérateur ne prend pas le type entier, ou si la compilation signale l'opérateur : remplacer GEL-OP par GEL-IV, Utilitaires|Tableau|IsValidIndex (relu : STE-H3d-IMPL.md:68, STE-H10-IMPL.md:59 ; TargetArray entrée 0, IndexToTest entrée 1, ReturnValue sortie 0). GEL-IV.TargetArray ← VariableGet_546.Results ; IndexToTest = 2000 (set_pin_value). IsValidIndex(2000) est vrai si et seulement si n ≥ 2 001 (GenericArray_IsValidIndex → FScriptArrayHelper::IsValidIndex, 0 ≤ i < Num, UE\Runtime\CoreUObject\Public\UObject\UnrealType.h:4295-4298, KismetArrayLibrary.cpp:583-595). Polarité inversée : GEL-IV.ReturnValue → GEL-B.Condition ; GEL-B.then (n > 2 000) → MacroInstance_1.Exec ; GEL-B.else → CallFunction_132.execute. Même volume (2 nœuds, 1 coupure, 5 liens, 1 set_pin_value), même total 909 ; seul MacroInstance_1.Exec et CallFunction_132.execute changent de source (GEL-B.then / GEL-B.else).
  • Repli de fond si le hitch subsiste après la garde : C1 (tri natif, DIAG § 4.1), nouvelle SPEC.

4. Risques R#, questions Q#

  • R-GEL1-1 : au-delà de 2 000, la liste montre les 1 000 premiers bâtiments de l'import, pas les plus proches (§ 1.4) ; effet voulu par la décision du 2026-10-14, à dire à la VAL.
  • R-GEL1-2 : la garde supprime le n², pas le coût linéaire (copies natives, 1 000 widgets) : hitch résiduel non mesuré ; V6 fixe le seuil à 2 000 ms.
  • R-GEL1-3 : typage de l'opérateur promotable en entier : relu pour des flottants et des objets (STG-B7a-IMPL.md:55, 58), pas pour des entiers ; suffixe <= relu en liste abrégée seulement. Repli (b).
  • R-GEL1-4 : le temps de 34 s est attribué au n² par la structure du graphe (établi) et par le moteur (estimé), sans Insights (D3 non fait) ; si V6-a échoue, le coût est ailleurs (copies, widgets) : ne pas rouvrir SortByDistance, repartir d'un DIAG.
  • R-GEL1-5 : appelants non couverts (§ 1.2) : Person (4 appels en 5.5), StreetMap_Static (fonction homonyme), autres graphes de TopRight ; SortByDistance sur tableau vide ne se termine pas (estimé) : hors C2.
  • R-GEL1-6 : TopRight et StreetMap non modifiés (MD5, git status), mais GI, Person, Room sont M dans l'arbre : ne pas les mettre au ticket.
  • R-GEL1-7 : LPDebugSubsystem.cpp est M : les lignes citées (§ 6, § 7) sont celles de l'arbre de travail, pas du HEAD (le DIAG citait 664-688) ; le build de V6 doit être compilé depuis cet arbre.
  • Q-GEL1-1 (utilisateur, facultative) : afficher un signe quand la liste n'est pas triée (texte « résultats non triés ») ? Non spécifié : la décision ne le demande pas.
  • Q-GEL1-2 (orchestrateur) : Person.recommend* (Q2 du DIAG) et le doublon StreetMap_Static.SortByDistance : hors C2, non tranchés ; un dump -BP=StreetMap_Static -Fn=SortByDistance et un find_nodes sur Person les établiraient.
  • Q-GEL1-3 (orchestrateur) : variante (a) (<=, conforme à la fiche) avec (b) en repli sans nouvelle SPEC, ou (b) d'emblée (type_id 100 % relu, polarité inversée). Recommandation : (a) puis (b).
  • Q-GEL1-4 (orchestrateur) : build de V6 : le prochain build COMMUN, lancé avec -ScriptStackOnWarnings -LPHitchLog (sans -LPHitchLog, aucune ligne de hitch : § 7).

5. Interdits

  • Aucune modification de SortByDistance, de IfThenElse_6, de CallFunction_139 (B = 1), de CallFunction_1338 (B = 1000), du PrintString, de DetermineBirdviewLooking, des autres graphes de TopRight, de GI, Person, StreetMap_Static, du C++ et du plugin StreetMap.
  • Aucun delete_node, aucun AND / OR / Select pur, aucun FunctionResult créé, aucun type_id non relu (P5), aucune écriture HyperAIStudio (outils Epic seulement, lp-mcp § 1.1), aucune fusion avec une autre IMPL sur TopRight.
  • Pas d'IMPL sans P0 à P5. Pas de commit par l'agent : ticket au gardien (lp-repo).

6. Valeurs moteur lues dans le moteur installé (UE = D:\Game\Launcher\Engine\UE_5.8\Engine\Source\)

  • UKismetMathLibrary::MinOfFloatArray(const TArray<float>&, int32&, float&) : UE\Runtime\Engine\Classes\Kismet\KismetMathLibrary.h:764 ; corps UE\Runtime\Engine\Private\KismetMathLibrary.cpp:406-409 (MinValue = FMath::Min(FloatArray, &IndexOfMinValue)).
  • FMath::Min(const TArray<T>&, int32* MinIndex) : UE\Runtime\Core\Public\GenericPlatform\GenericPlatformMath.h:1026-1054 ; tableau vide → INDEX_NONE et T() (1028-1035) ; boucle linéaire (1039-1047).
  • Conversion de conteneur double → float d'un tableau passé à un paramètre float : UE\Editor\KismetCompiler\Private\KismetCastingUtils.cpp:240-244.
  • Nœud pur incorporé à chaque nœud consommateur : UE\Editor\KismetCompiler\Private\KismetCompiler.cpp:2881-2897.
  • GenericArray_Length (Num() ou 0 si nul) : UE\Runtime\Engine\Private\KismetArrayLibrary.cpp:411-421 ; GenericArray_IsValidIndex : 583-595 ; FScriptArrayHelper::IsValidIndex = Index >= 0 && Index < Num() : UE\Runtime\CoreUObject\Public\UObject\UnrealType.h:4295-4298.
  • Garde de boucle infinie : MaximumLoopIterationCount=100000000 (Config\DefaultEngine.ini:51).
  • Journal de hitch (code du jeu, arbre de travail) : option -LPHitchLog et seuil 50 ms ou -LPHitchMs= (LD:652-654) ; sans l'option, aucune mesure (LD:723-726) ; ligne LPDebug hitch : %.1f ms (seuil %.1f) #%d en Warning, 100 premières lignes puis une sur 25 (LD:729-738, condition l. 733) ; au démarrage LPDebug hitch : actif, seuil 50.0 ms (LD:663) ; bilan ... max %.1f ms (LD:748-753) par la commande console LifePlay.HitchSummary (LD:617-624).
  • Non lu (estimé) : la macro WhileLoop (asset StandardMacros) et donc le nombre exact d'évaluations de la Condition ; le remplissage de OSMFile.Ways ; le code de FilterByAmenity après SM:139.

7. Critères de VAL (commande → attendu)

V5 : VAL statique (éditeur fermé, Tools\UeState.ps1 = 0 avant et après)

  1. Entrée : md5sum Content/UI/TopRight.uasset (noté, ≠ 1fcb94a6… après l'IMPL) ; git status --short -- Content : TopRight s'ajoute aux M déjà présents sans en modifier (MD5 identiques avant / après).
  2. Dump EventGraph : .\Tools\RunCommandlet.ps1 -Lot STE-GEL1V -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=EventGraph -Pins -Exec' -EtatDir "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE" → code=0, journal : === TopRight :: EventGraph (909 noeuds) === (premier listing seul, les -Pins -Exec sont doublés : STE-H3g-VAL.md:43). Liens attendus : CallFunction_7736.then → GEL-B.execute seul ; CallFunction_132.execute ← GEL-B.then seul ; MacroInstance_1.Exec ← VariableSet_26.then, IfThenElse_6.else, GEL-B.else ; GEL-B.Condition ← GEL-OP.ReturnValue ; GEL-OP.A ← CallArrayFunction_11.ReturnValue (qui garde CallFunction_139.A), GEL-OP.B = 2000 ; GEL-B : then seul vers CallFunction_132, else seul vers MacroInstance_1. Variante (b) : GEL-IV.TargetArray ← VariableGet_546.Results, IndexToTest = 2000, polarité inversée (§ 3).
  3. Dump SortByDistance : même commande avec -Fn=SortByDistance → === TopRight :: SortByDistance (29 noeuds) === et listing 1 identique à SBD:1-129 (nœuds et liens comparés en ensembles triés ; Tools\DiffRemplacementNoeuds.py non applicable, STE-H3g-VAL.md:42).
  4. Diff EG avant / après (avant = EG listing 1, l. 1-4188 ; après = premier listing du dump 2) : 2 nœuds nouveaux, 0 disparu ; broches modifiées sur nœuds communs : CallFunction_7736 (then), CallFunction_132 (execute), MacroInstance_1 (Exec), CallArrayFunction_11 (ReturnValue) et rien d'autre ; 1 lien coupé, 5 nouveaux (comptés orientés, comme STE-H3g-VAL.md:33).
  5. Compilation : compile_blueprint … warnings_as_errors:true → null à l'IMPL, journal éditeur sans Error / Warning pour TopRight ; .\Tools\RunCommandlet.ps1 -Lot STE-GEL1V -Commandlet CompileAllBlueprints -EtatDir … : Grep Error:.*/Game/ → 0 ; CC_Shaders à part (référence STE-H3g-VAL.md:35 : code=3, 25 erreurs, 3 erreurs de compilation, 55 avertissements, 420 a réussi, identiques) ; une ligne La compilation de TopRight a réussi.
  6. LPTestStringCompat : .\Tools\RunCommandlet.ps1 -Lot STE-GEL1V -Commandlet LPTestStringCompat -EtatDir … → === 3120 cas : compat != herite(UE4) : 0 ; moteur != herite(UE4) : 60 === (STE-H3g-VAL.md:36).
  7. Instantané intact : md5sum D:\Game\LifePlay_Backup_5.5\STE-GEL1\Content\UI\TopRight.uasset = 1fcb94a60061b418e9deeb08bc1a990d.

V6 : essai en jeu (build Development empaqueté par BUILD, ligne de commande -ScriptStackOnWarnings -LPHitchLog)

  • Preuve que le parcours est rejoué : journal avec LPDebug hitch : actif, seuil 50.0 ms (LD:663) ; monde CentralLondon ; vue aérienne ; panneau de recherche ouvert. Sans -LPHitchLog, aucune ligne de hitch n'existe (LD:723-726) : un « aucun hitch » ne prouve alors rien.
  • V6-a (cas du gel) : CentralLondon, vue aérienne, terme vide, filtre « All », valider. Attendu : une ligne LogBlueprintUserMessages: [TopRight_C_…] X=… Y=… at least 2 result (forme de J5:1830) puis aucune ligne LPDebug hitch : <ms> supérieure ou égale à 2 000 ms dans cette image ni la suivante (avant : 34229.3 ms, même image [214], J5:1830-1831). Mesure : .\Tools\LogStats.ps1 -Latest <journal> -Pattern 'at least 2 result','LPDebug hitch' ; piège : les lignes de hitch s'arrêtent à 100 puis une sur 25 (LD:733) ; faire l'essai en début de session, ou lire max par LifePlay.HitchSummary (LD:748-753) juste après (le max est celui de toute la session). Cible visée : moins de 1 500 ms (estimée). Affichage : au plus 1 000 widgets (contrôle visuel).
  • V6-b (le tri est conservé) : recherche ciblée (terme ou filtre donnant entre 2 et 2 000 résultats) depuis une même vue (la vue est imprimée : X=… Y=…) : les 20 premiers résultats sont identiques, dans l'ordre, à ceux d'un build d'avant (même terme, même vue) ; la ligne at least 2 result est présente ; hitch de la recherche du même ordre qu'avant (moins d'1 s attendu : 6 635 bâtiments ≈ 0,6 s, DIAG § 3.4, estimé).
  • V6-c (seuil) : le nombre exact n n'est pas journalisé (D4 non fait, impression temporaire interdite) : V6 ne contrôle pas la frontière 2 000 / 2 001 ; elle est établie par la valeur 2000 lue au dump (V5-2). Non mesuré par V6.
  • Non-régression : fatal|Unhandled|Critical|Assertion → 0 ; .\Tools\LogStats.ps1 -Compare <journal>,<J5> : aucune famille nouvelle ; si le build n'existe pas encore, V6 est reporté à la prochaine session COMMUN (précédent : STE-H3g-VAL.md:39).

Fichiers à commiter (dépôt jeu, branche ue5.8, ticket au gardien) :

  • A Docs/Migration/etat/ST/STE/STE-GEL1-SPEC.md

Tours : 27 / 40 (comptés à la main, Write compris ; Tools/FicheStats.py fait foi)

Constats

Commandes (outils Grep / Read / Glob sauf mention) et comptes bruts :

  • C1 Bash : git rev-parse --short HEAD → 0745917 ; md5sum Content/UI/TopRight.uasset → 1fcb94a60061b418e9deeb08bc1a990d ; git status --short -- Content/UI/TopRight.uasset Plugins/StreetMap → aucune ligne ; git log --oneline -1 -- Content/UI/TopRight.uasset → f32df0d.
  • C2 En-têtes : SBD:1 (29 noeuds), SBD:130 (29 noeuds) ; EG:1 (907 noeuds), EG:4189 (907 noeuds). Grep SortByDistance sur EG → 1 ligne (2613) ; Grep at least sur EG → 1 ligne (3366). Grep variable 'Results' sur EG → 2581, 2589, 2598, 2607, 2620.
  • C3 Bash (lecture, binaires : le Grep s'arrête au premier octet nul, STE-H3g-SPEC.md C8) : grep -rlaF "SortByDistance" Content/BPs Content/UI Content/Maps --include=*.uasset --include=*.umap → Content/BPs/Person.uasset, Content/BPs/StreetMap_Static.uasset, Content/UI/TopRight.uasset (+ grep: Content/Maps: No such file or directory, code 2) ; même commande sur Content/Scenes Structs Developers Collections Render Characters → aucune sortie ; grep -a -o -E "[A-Za-z0-9_/.:]{0,50}SortByDistance[A-Za-z0-9_/.:]{0,30}" : StreetMap_Static → 12 SortByDistance, 1 SortByDistance.K2Node_FunctionEntry_9, 1 SortByDistance.K2Node_FunctionResult_0 ; Person → 1 SortByDistance ; contrôle TopRight (grep -a -o -F … | wc -l) → 16.
  • C4 Grep SortByDistance : Content\Modules → 0 ; Source → 0 ; Plugins (*.cpp,h,ini,py,json,md) → 0 ; L6f_dump_Person.txt → 9842, 9979, 10167, 10519 ; Person__graphes_D9.txt (en-tête === Person : 134 graphe(s) ===) → 0.
  • C5 Lectures moteur : § 6 (toutes relues le 2026-10-14) ; FMath::Min avec indice en GenericPlatformMath.h:1026-1054.
  • C6 J5:1829-1831 relues : … [TopRight_C_2147482047] X=20411.688 Y=0.000 at least 2 result puis LPDebug hitch : 34229.3 ms (seuil 50.0) #12, même image [214].
  • C7 Sources de chiffres relues : Docs\Journal_Modifications.md:820 (49 425 bâtiments, 5 191 aménités, CentralLondon) ; U8_LPCheckStreetMap_20261011-003558.txt:52-53 (53 050 bâtiments, 6 635 avec amenity, 280 villes) ; Docs\18_Build_Debug.md:234 (FindCenterOfPolygon 0,742 s inclus, 49 425 appels).
  • Écarts à la fiche : (1) la fiche cite Source\LifePlay\StreetMap.cpp:87-90 ; ce fichier n'existe pas (Glob **/StreetMap.cpp sur Source : aucun) ; le code est à Plugins\StreetMap\Source\StreetMapRuntime\StreetMap.cpp:87-90 (mêmes lignes) ; (2) plan du livrable : gabarit lp-doc (sections 1 à 7) avec la correspondance de la fiche en tête ; (3) Bash en lecture pour grep -a sur binaires, git, md5sum, ls (le Grep n'ouvre pas les .uasset par dossier) ; aucun refus du crochet, aucun caractère > dans les commandes.
  • Non mesuré : la durée de chaque phase de SortByDistance (D3 Insights) ; le nombre d'évaluations de MinOfFloatArray par tour ; n à l'image 214 (49 425 estimé) ; la répartition des aménités par type ; le coût résiduel de la boucle de 1 000 widgets ; le contenu de StreetMap_Static.SortByDistance ; les appelants de Person en 5.8 ; les autres graphes de TopRight.
  • Lecture seule respectée ; un seul fichier écrit (ce livrable, non suivi). Aucun nom d'utilisateur ni chemin de profil cité.

Décision de l'orchestrateur (2026-10-14)

  • Q-GEL1-3 : variante (a) (<=), repli (b) sans nouvelle SPEC si P5 ne trouve pas le type_id.