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

39 KiB
Raw Blame History

Fiche STE-H8 — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-opus-5-5 — Agent lp-cart (DIAG, lecture seule) — Skills sk-protocol, lp-commandlets (lecture), lp-paths, ue-cpp-build (préchargés)

STE-H8-DIAG : saccades de fin de scène et TopRight.GenerateRecs (2026-10-11)

Hors gabarit (partiel) : le diagnostic porte sur deux volets et non sur une seule hypothèse, à la demande de l'orchestrateur (correction du 2026-10-11, après recompte de STE-H4-MESURE.md § 4) :

  • volet a : la trame de fin de scène ;
  • volet b : les trames où tourne GenerateRecs.

Résumé :

  • Volet a, fin de scène. La saccade vue en fin de scène est la trame de fin de scène elle-même : 256 à 797 ms dans M-H4, 144 à 331 ms dans J5. GenerateRecs ne tourne qu'à la trame suivante, déclenché par un Delay de 0,2 s. Cette trame dure 18 à 56 ms (8 cas mesurables sur 12).
  • Volet b, GenerateRecs. Je retrouve les 8 trames longues du recompte corrigé (2 107 ms). Six sont des arrivées dans un lieu (1 823 ms), pas des fins de scène. Deux seulement suivent une fin de scène : 56 ms, et 228 ms qui se terminent sur une ligne LLM.
  • Ce qui reste ouvert. À l'arrivée dans un lieu, 90 à 313 ms passent juste après l'impression de GenerateRecs. Le partage de ce temps entre GenerateRecs et le code qui le suit reste indécis faute de dump.

1. Symptôme et reproduction

  • Symptôme initial : STE-H4-MESURE.md:39 compte 13 trames, 4,9 s, classe « GenerateRecs (TopRight, juste après une fin de scène) ». STE-H4-MESURE.md:50-52 en tire comme cause probable (estimée) TopRight.GenerateRecs.

  • Recompte corrigé par l'orchestrateur (message du 2026-10-11, rapporté, non rejoué en entier ici). La durée est attribuée au contenu de la trame n−1 (136 trames ≥ 50 ms, 29,6 s) :

    • fin de scène (DESTROY, CumOn Clear, DestroyDummy was from DestroyPerson) : 14 trames, 4,3 s ;
    • GenerateRecs : 8 trames, 2,1 s ;
    • SPAWN : 70 trames, 11,2 s ;
    • selon l'agent H7, les 8 trames GenerateRecs font 2 230 ms, dont 1 410 ms de TimerManager.
  • Journal de mesure : copie de lecture %USERPROFILE%\AppData\Local\Temp\claude\D--Game-LifePlay-master-5-8\37530d8d-c780-4a57-8fbd-bcb010aa6408\scratchpad\M-H4-LifePlay.log, lancée avec -llm -llmcsv (STE-H4-MESURE.md:9). Grep GenerateRecs → 30 lignes :

    • 28 lignes [TopRight_C_2147482028] GenerateRecs: <valeur> (l. 1258 à 24913) ;
    • 2 lignes [PlayerCharacter] GenerateRecs: Found Home (l. 1259 et 1758).
  • Second échantillon : ...\scratchpad\J5-LifePlay.log (45 lignes TopRight, 17 lignes FOLLOWUP, 0 Found Home ; script 2, § 3.7).

  • Reproduction sans jeu (la rubrique 6 de la fiche l'interdit) : relecture des deux journaux par les scripts du § 3.7, en lecture seule, sans rien écrire.

  • Ancienne méthode reproduite (établi), celle de mesure_h4.py:88-93 : début(n) − début(n−1), classé selon le contenu de n. Restreinte aux trames qui contiennent GenerateRecs (script 2), elle donne :

    • 13 trames ≥ 50 ms : 807, 270, 153, 398, 323, 412, 453, 261, 297, 409, 353, 295 et 427 ms ;
    • somme 4 858 ms (calcul à la main), soit les 4,9 s de STE-H4-MESURE.md:39.
  • Méthode corrigée reproduite (établi) : la durée de la trame qui contient GenerateRecs, du début de cette trame au début de la suivante (colonne dur du script 1, quand la trame suivante est consécutive). Elle donne 8 trames ≥ 50 ms pour 2 107 ms :

    Ligne Durée (ms)
    1757 198
    3268 56
    4361 252
    7752 445
    8479 260
    12827 228
    19150 399
    21344 269

    Ce sont les 8 trames et les 2,1 s du recompte. Le chiffre de 2 230 ms de l'agent H7 n'est pas rejoué.

  • Colonne TimerManager : Exclusive/GameThread/TimerManager existe dans les captures CSV (établi, Grep : l. 1 et 9002 de ...\STE-J5_Development\Windows\LifePlay\Saved\Profiling\CSV\Profile(20261011_012223).csv et (…012509).csv). La part de 1 410 ms est rapportée et n'a pas été recalée ici sur les trames du journal (incertain).

2. Hypothèse testée

H8 (fiche) : les saccades de fin de scène viennent du coût de TopRight.GenerateRecs.

Testée sur deux volets (voir l'en-tête) :

  • a. Où est le temps dans la trame de fin de scène, et quelle part y prend GenerateRecs ?
  • b. Les 8 trames longues où tourne GenerateRecs sont-elles des fins de scène, et qu'est-ce qui y coûte ?

3. Essai et résultat brut

3.1 Qui appelle GenerateRecs (Q-a) : établi sur le dump GI V5

Source : etat\ST\STE\dumps\GI__EventGraph_V5.txt (GI :: EventGraph, 6 234 nœuds, l. 1). Le GI n'a pas changé depuis : git log -1 -- Content/BPs/GI.uasset → 4283c13. Le dump contient trois nœuds d'appel TopRight_C::GenerateRecs(). « STB-GIA » désigne etat\ST\STB\STB-GIA_LPDumpFunction_20261009-204233.txt (dump du 2026-10-09).

Chemin Nœud d'appel (V5) Ce qui le déclenche (exécution) Ce qui suit Quand
C, fin de scène K2Node_CallFunction_313 (l. 20017-20020) Chaîne : DelayThenFollowUp (K2Node_CustomEvent_34, l. 18757-18759) → PrintString(followUp + ' FOLLOWUP') (K2Node_CallFunction_826, l. 25844-25851) → IfThenElse_19.else (l. 18039-18043, condition Len(followUp) > 0 aux l. 18030-18038) → Delay 0,2 s (K2Node_CallFunction_119, l. 20050-20056) Room_C::getKnownNpcsHerePeriod() (K2Node_CallFunction_774, V5 l. 25010 ; then vide : STB-GIA:25129-25131) fin de scène sans suite (followUp vide)
B, horaire K2Node_CallFunction_312 (l. 20010-20013) Chaîne : Set CurrentHour (V5 l. 45-47) → UpdateSkylight (STB-GIA:23440-23442) → IfThenElse_55.then (l. 20002-20006), condition CurrentHour != LastHourRec (l. 19994-20000) getKnownNpcsHerePeriod (K2Node_CallFunction_780, V5 l. 25057 ; STB-GIA:25176-25178), puis Set CurrentMin changement d'heure du jeu
A, fin d'action K2Node_CallFunction_302 (l. 19973-19976) Chaîne : StreetMap_Static.ClearBubbles (V5 l. 22391-22393) → ForEachLoop(CurrentEndEffects).Completed (V5 l. 2738-2744) → DestroyPerson(Interacting, WithoutDestroying=true) (K2Node_CallFunction_721, V5 l. 24405-24411) → Set CurrentActionDes (K2Node_VariableSet_3136, STB-GIA:2889-2891) TopRight_C::Current(CurrentAmenity) (K2Node_CallFunction_409, STB-GIA:21699-21703), puis IfThenElse_1302 (AutoPauseAfterAction, V5 l. 15512-15516) fin normale d'une action
  • Les trois PrintString qui étiquettent les chemins ont une entrée d'exécution vide et ne s'impriment donc jamais (établi par le dump ; aucune ne figure parmi les 30 lignes du journal) :

    • 'GenerateRecs: Action ends normally' (nœud 309, l. 19977-19982) ;
    • 'generaterecs: Hourly' (314, l. 20021-20026) ;
    • 'generaterecs: SceneEnd' (315, l. 20032-20037).
  • Les entrées 2 et 3 du Delay 119 (VariableSet_278 et IfThenElse_140.else) sont mortes (établi) :

    • IfThenElse_140 a une entrée d'exécution vide (l. 25153-25157) ;
    • 277 → 278 ne part que de 140.then (l. 25158-25166).
  • Autres assets. Grep GenerateRecs sur chaque fichier passé explicitement :

    Asset Occurrences Remarque
    TopRight.uasset 8
    GI.uasset 2
    Person.uasset 2 texte Found Home
    BPs\PC, BPs\Room, StreetMap_Static, UI\Cursor 0

    Le reste de Content n'a pas été cherché : un Grep sur un dossier saute les binaires (Content avec *.uasset → 0, alors que TopRight.uasset seul → 8). D'autres appelants ne sont donc pas exclus (incertain).

  • Minuterie (lien avec les 1 410 ms de TimerManager, estimé) :

    • le chemin C passe par un Delay, c'est-à-dire une action latente, et non par une minuterie (dump V5, nœud 119) ;
    • TopRight.uasset contient K2_SetTimerDelegate et K2_ClearAndInvalidateTimerHandle dans sa table de noms (Grep -o) ;
    • lecture : la chaîne d'arrivée tourne probablement dans un rappel de minuterie (fin d'action ou minuterie de TopRight). Le dump le dira.

3.2 Combien de fois, à quelle fréquence (Q-a)

Sortie brute du script 2 (§ 3.7). Le chemin B est repéré par une heuristique : valeur à moins de 0,002 h d'une heure entière.

Journal Appels TopRight C, fin de scène B, heure Autres
M-H4 (23.22.55.566 → 23.30.08.985, 433 s) 28 12 5 11
J5 45 17 13 15

Lecture des comptes :

  • Dans M-H4, la l. 1258 (9.000926), classée B par l'heuristique, est le premier chargement de pièce (LOAD ROOM à la l. 1246). Je la compte avec les arrivées : 12 C, 4 B et 12 arrivées dans un lieu.
  • GenerateRecs tourne une fois par fin de scène (établi) :
    • M-H4 : 12 lignes FOLLOWUP (l. 3147, 7193, 12758, 15071, 16466, 18873, 19510, 21469, 23105, 24417, 24636, 24810), chacune suivie d'un GenerateRecs dans la trame suivante ;
    • J5 : 17 lignes et 17 appels.
  • Fréquence dans M-H4 : un appel toutes les 15,5 s en moyenne, et un toutes les 36 s en fin de scène.
  • Arrivées dans un lieu :
    • dans la même trame, l'impression est suivie de [StreetMap_Static] 310 CHECK FOR clOSE <lieu> (l. 4832, 4868, 7753, 8480, 19109, 19151, 21345) ;
    • dans les deux cas relus, elle est précédée de [Room_C_0] <lieu>.lpworld test0203 (l. 4830 → 4831 et 7751 → 7752).
    • Leur rattachement au nœud 302 (chemin A) est estimé : le chemin A n'a pas d'impression qui lui soit propre.

3.3 Ce qu'il calcule et ce que vaut la valeur imprimée (Q-a)

  • Valeur imprimée. Les 28 valeurs croissent de 9.000926 (l. 1258) à 16.053429 (l. 24913). Quatre sont à moins de 0,001 d'une heure entière, ce qui colle au chemin B : 11.000883 (l. 6148), 14.000546 (l. 15623), 15.000269 (l. 18619) et 16.00065 (l. 24703). Estimé : c'est l'heure du jeu en heures décimales (14,12 ≈ 14 h 07). Le texte GenerateRecs: est dans TopRight.uasset (Grep -o : 2 occurrences), donc c'est TopRight qui imprime. La place de l'impression dans la fonction (début ou fin) reste incertaine sans dump.
  • Contenu, estimé d'après la table de noms de TopRight.uasset (Grep -o) :
    • noms trouvés : ClearRecs, LastHourRec, recommendActions, RecommendedActions, RecommendedBuildings, RecommendedKeyActions, RecommendedKeyBuildings, RecBox, RecActions, Recommend_C, K2Node_CreateWidget, SortByDistance, SortedBuildings, DistanceFloats, FindCenterOfPolygon, K2_SetTimerDelegate ;
    • lecture : GenerateRecs vide RecBox, fait calculer les recommandations par le personnage joueur, trie des bâtiments par distance, puis crée un widget Recommend par entrée.
  • Chaîne côté Person, dump 5.5 etat\portage55\L6f\L6f_dump_Person.txt. Person a été modifié depuis (ee2821a, 2026-10-09) : tout ce qui suit est à reconfirmer en 5.8 (STE-D9).
    • recommendActions (143 nœuds, l. 9013) appelle recommendKeyActions (9017), GI.CheckCondition (9113), GI.getAmenitiesForAction (9237), StreetMap.isAmenityOpen (9474, 9587), StreetMap.SearchByAmenitySafe (9688) et StreetMap.CalculateDistanceSafe (9711).
    • recommendKeyActions (l. 9724) appelle recommendHomeIfNeeded, recommendJobIfNeeded, recommendBasicNeeds et recommendCompanion (9743-9755).
    • Ces quatre fonctions appellent à leur tour StreetMap.SearchByAmenitySafe (9926, 10137, 10658, 10664), TopRight.SortByDistance (9842, 9979, 10167, 10519) et GI.getActionDetail (6 appels, l. 10111 à 10551).
    • Le texte GenerateRecs: Found Home est dans recommendHomeIfNeeded (l. 9920 ; la fonction couvre les l. 9770-9931).

3.4 Volet a : la trame de fin de scène et la trame GenerateRecs qui la suit (journal M-H4)

Définitions :

  • trame n : celle qui contient GenerateRecs ; n−1 : la trame précédente, qui est celle de la fin de scène ;
  • durée n−1 : début(n) − début(n−1), soit la méthode corrigée appliquée à n−1 ;
  • étendue n−1 : dernière ligne − première ligne de n−1 ;
  • trame n entière : début de n → début de n+1 ;
  • après l'impression : de la ligne GenerateRecs à la première ligne de n+1.

Sources : scripts 1 et 2 (§ 3.7).

Ligne Trame n Durée n−1 (ms) Étendue n−1 (ms) FOLLOWUP → GenerateRecs (ms) Trame n entière (ms) Après l'impression (ms) n+1 consécutive
3268 546 807 797 180 56 30 oui
7258 28 270 262 141 26 14 oui
12827 866 398 361 154 228 225, jusqu'à une ligne Wrote LLM csv oui
15147 830 323 287 184 19 16 oui
16540 756 412 377 157 18 14 oui
18979 630 453 440 368 22 14 oui
19544 805 261 256 171 — 337, jusqu'à une ligne LLM non
21493 886 297 281 151 — 153 non
23184 590 409 405 157 24 19 oui
24484 325 353 317 203 21 20 oui
24662 175 295 285 157 — 988, jusqu'à une ligne LLM non
24913 872 427 417 278 27 15 oui

Lecture (établi sauf mention) :

  • Où est le temps à chaque fin de scène. La ligne FOLLOWUP et le gros du temps sont dans n−1 :
    • durée de 261 à 807 ms, somme 4 705 ms ;
    • étendue de 256 à 797 ms, médiane 339 ms, somme 4 485 ms ;
    • GenerateRecs s'imprime ensuite dans n, 0 à 26 ms après le début de n ;
    • exemple : la trame 629 va de la l. 18873 ( FOLLOWUP, 23.27.59:159) à la l. 18937 (CumOn Clear, :506) ; GenerateRecs s'imprime à la l. 18979 (:527), dans la trame 630.
  • Lien avec le recompte de l'orchestrateur (estimé) : ses 14 trames de fin de scène (4,3 s) recouvrent ces 12 trames n−1. La trame 545 (807 ms) contient aussi une ligne index SPAWN (journal l. 3121) et peut être comptée en SPAWN, selon l'ordre des classes. Les critères diffèrent : DESTROY, CumOn, DestroyDummy chez l'orchestrateur ; FOLLOWUP ici.
  • Part de GenerateRecs après une fin de scène. La trame n dure 18 à 56 ms dans 8 cas sur 12. Les 4 autres :
    • l. 12827 : 228 ms, qui se terminent sur une ligne Wrote LLM csv, c'est-à-dire l'instrument de mesure (STE-H4-MESURE.md:58-59) ;
    • 3 cas sans borne : n+1 n'a aucune ligne de journal, et l'écart couvre plusieurs trames (2 de ces écarts finissent sur une ligne LLM).
  • Ce que voit le joueur à chaque fin de scène : une trame de 261 à 807 ms (n−1), puis une trame de 18 à 56 ms (n). La part de GenerateRecs est au plus de 56 ms, sauf dans un cas, perturbé par une ligne LLM.
  • Second échantillon J5 : 17 appels C ; étendue n−1 médiane 205 ms (144 à 331 ms) ; après l'impression, au plus 17 ms (médiane 15) dans les 10 cas où n+1 est consécutive.
  • Pourquoi GenerateRecs tombe juste après la trame longue.
    • Établi : DelayAction.h:29-30 (UE 5.8) retire Response.ElapsedTime() du temps restant et déclenche dès qu'il est ≤ 0.
    • Établi : GenerateRecs se déclenche dans n alors que 141 à 184 ms seulement se sont écoulés depuis FOLLOWUP dans 9 cas sur 12, soit moins que les 0,2 s. Le PrintString 826 et le Delay 119 se suivent sans nœud coûteux entre eux (V5 l. 18030-18043).
    • Estimé : l'écoulement compté est le delta de la trame n, égal à la durée de n−1. Une dilatation du temps aurait le même effet ; LatentActionManager n'a pas été relu.
    • Conclusion : la coïncidence vient du Delay et de la longueur de n−1, pas du coût de GenerateRecs.
  • L'ancienne méthode décale tout d'une trame (établi, mesure_h4.py:88-93) : elle classait la durée de n−1 selon le contenu de n. L'orchestrateur a corrigé ce point.

3.5 Volet a : où passe le temps de la trame de fin de scène (n−1)

Méthode : écarts ≥ 5 ms entre lignes consécutives des trames qui contiennent FOLLOWUP, regroupés par paire de lignes (script 3, § 3.7). « Ligne avant ⇒ ligne après » ; entre parenthèses, l'écart moyen par occurrence.

Écart M-H4 (12 trames, étendue totale 4 485 ms) J5 (17 trames, 3 642 ms)
MouseCaptureMode Changed, CapturePermanently -> … ⇒ HideCursorDuringCapture Changed, False -> True 855 ms, n = 12 (≈ 71 ms) 568 ms, n = 17 (≈ 33 ms)
HideCursorDuringCapture Changed, True -> False ⇒ … False -> True 826 ms, n = 12 (≈ 69 ms) 540 ms, n = 17 (≈ 32 ms)
une statistique de Person ⇒ la suivante (intelligence ⇒ muscle ; dans J5 attractiveness ⇒ martial) 233 ms, n = 7 (≈ 33 ms) 417 ms, n = 16 (≈ 26 ms)
ligne numérique ⇒ CumOn Clear 276 ms, n = 7 (≈ 39 ms) 388 ms, n = 16 (≈ 24 ms)
Dressed <vêtement> ⇒ ligne suivante, par vêtement 83 à 107 ms, n = 7 (≈ 12-15 ms) 89 à 118 ms, n = 14-16 (≈ 6-7 ms)
DestroyDummy was from DestroyPerson ⇒ Stripped … 83 ms, n = 4 86 ms, n = 9

Lecture :

  • Établi : la durée qui sépare deux impressions.
  • Estimé : ce temps est celui du code exécuté entre ces deux impressions, c'est-à-dire la bascule d'interface et de mode d'entrée en fin de scène, les modifications de statistiques, Dress et CumOn.
  • Ces quatre postes font l'essentiel des 256 à 797 ms. La ligne FOLLOWUP arrive au milieu de la trame (+89 à +652 ms dans M-H4, script 3), et une partie de ce travail la suit.
  • Sur les mêmes postes, M-H4 est environ deux fois plus lent que J5. Estimé : surcoût de -llm (STE-H4-MESURE.md:9). Les options de lancement de J5 n'ont pas été relues (incertain).

3.6 Volet b : les trames où tourne GenerateRecs (journal M-H4)

Les 8 trames du recompte corrigé, reproduites (§ 1) :

Ligne Durée de la trame (ms) Chemin Écart après l'impression Ligne qui suit l'écart
1757 198 arrivée 96 ms, même trame [PlayerCharacter] GenerateRecs: Found Home (l. 1758)
3268 56 C, fin de scène 30 ms, trame suivante No SkeletalMesh (l. 3269)
4361 252 arrivée (LOAD ROOM) 207 ms, trame suivante Piece exterieure (l. 4362)
7752 445 arrivée (pub) 303 ms, même trame CHECK FOR clOSE pub (l. 7753)
8479 260 arrivée (fast_food) 102 ms, même trame CHECK FOR clOSE fast_food (l. 8480)
12827 228 C, fin de scène 225 ms, trame suivante Wrote LLM csv line 175 (l. 12828)
19150 399 arrivée (art) 313 ms, même trame CHECK FOR clOSE art (l. 19151)
21344 269 arrivée (restaurant) 105 ms, même trame CHECK FOR clOSE restaurant (l. 21345)

Lecture :

  • Répartition (établi) : 6 trames sur 8 (1 823 ms) sont des arrivées dans un lieu, pas des fins de scène. Deux suivent une fin de scène : 56 ms, et 228 ms qui se terminent sur une ligne LLM.

  • Autres arrivées longues hors des 8. Leur trame suivante n'a pas de ligne de journal, si bien que la durée de leur trame n'est pas bornée. Les écarts mesurés dans la même trame sont pourtant longs :

    • l. 1258 → 1259 (Found Home) : 90 ms ;
    • l. 4831 → 4832 : 197 ms ;
    • l. 4867 → 4868 : 197 ms ;
    • l. 19108 → 19109 : 110 ms.

    Au total, 10 arrivées sur 12 passent au moins 90 ms dans la trame juste après l'impression. Dans J5, 3 arrivées sont dans ce cas : l. 1402 (82 ms), 12606 (114) et 14252 (118).

  • Chemin B (heure, 4 appels) : 0 ms après l'impression, et la trame suivante arrive à 16-17 ms (3 cas bornés). L'appel de la l. 6148 n'est pas borné.

  • Ce qui coûte à l'arrivée.

    • Dans les l. 1257 → 1258 et 1757 → 1758, 90 et 96 ms séparent l'impression de TopRight du Found Home imprimé par recommendHomeIfNeeded. Si la chaîne GenerateRecs → recommendActions → recommendKeyActions → recommendHomeIfNeeded est la bonne (estimé, § 3.3), au moins 90 ms sont dans GenerateRecs.
    • Pour les autres arrivées, l'écart court jusqu'à CHECK FOR clOSE <lieu>. Il couvre aussi le code qui suit GenerateRecs, par exemple TopRight.Current au chemin A (estimé). Le partage est indécis.
    • Les 1 410 ms de TimerManager rapportés (sur 2 230 ms) vont dans le sens d'une chaîne exécutée dans un rappel de minuterie (estimé, § 3.1). Le recalage des trames CSV sur le journal n'est pas refait ici (incertain).
    • Postes candidats (estimés) : recherches SearchByAmenitySafe sur tous les bâtiments d'un type, tris SortByDistance avec un FindCenterOfPolygon par bâtiment, CheckCondition par action, création des widgets Recommend.
    • Postes écartés : ces trames ne contiennent pas de lecture .lpscene ni de CHECKING: (ces lignes sont réparties à raison d'une par trame, par exemple l. 4812-4829).
  • Contraste : 14 à 30 ms après l'impression en fin de scène, 90 à 313 ms à l'arrivée. La raison est incertaine : une branche qui dépend du lieu, ou le coût de Current. Le dump de GenerateRecs tranchera.

  • Proportion (Q-b) :

    • 30 lignes au total : 28 de TopRight et 2 de Found Home, ces dernières dans les trames 1258 et 1757 ;
    • 8 appels sur 28 tournent dans une trame bornée ≥ 50 ms ;
    • 11 sur 28 si l'on compte les arrivées non bornées dont l'écart dans la même trame dépasse 50 ms (1258, 4831, 4867, 19108) et qu'on retire la l. 3268 (56 ms, fin de scène, et seulement 30 ms après l'impression). Le décompte est 10 arrivées plus la l. 12827.

3.7 Commandes (lecture seule, lancées depuis Git Bash, aucun fichier écrit)

SP=%USERPROFILE%/AppData/Local/Temp/claude/D--Game-LifePlay-master-5-8/37530d8d-c780-4a57-8fbd-bcb010aa6408/scratchpad. Le regroupement par trame reprend l'expression de mesure_h4.py:69. Colonne TimerManager : Grep [^,]*TimerManager[^,]* (-o) sur ...\STE-J5_Development\Windows\LifePlay\Saved\Profiling\CSV.

Script 1 : détail par appel, journal M-H4. Il donne dur (trame n entière, méthode corrigée), back ( FOLLOWUP → GenerateRecs) et les écarts internes.

python -I - "$SP/M-H4-LifePlay.log" <<'EOF'
import re,sys
from datetime import datetime
log=sys.argv[1]
ts=re.compile(r'^\[(\d{4})\.(\d\d)\.(\d\d)-(\d\d)\.(\d\d)\.(\d\d):(\d{3})\]\[\s*(\d+)\]')
L=[]
with open(log,encoding='utf-8',errors='replace') as fh:
    for n,line in enumerate(fh,1):
        m=ts.match(line)
        if not m: continue
        y,mo,d,h,mi,s,ms,fr=m.groups()
        t=datetime(int(y),int(mo),int(d),int(h),int(mi),int(s),int(ms)*1000).timestamp()
        L.append((n,t,int(fr),line.rstrip('\n')))
frames=[]
for i,(n,t,fr,tx) in enumerate(L):
    if not frames or frames[-1][0]!=fr: frames.append([fr,i,i])
    else: frames[-1][2]=i
fidx={}
for k,f in enumerate(frames):
    for i in range(f[1],f[2]+1): fidx[i]=k
keys=(' FOLLOWUP','DestroyDummy was from DestroyPerson','CumOn Clear','UPDATE TIME','FlushAsyncLoading','Found Home','Wrote LLM','index SPAWN','DESTROY','CHECKING:','LOAD ROOM')
for i,(n,t,fr,tx) in enumerate(L):
    if 'GenerateRecs:' in tx and 'TopRight' in tx:
        k=fidx[i]; f=frames[k]
        t0=L[f[1]][1]; t1=L[f[2]][1]
        nxt=frames[k+1]; tn=L[nxt[1]][1]
        cn=(nxt[0]-fr)%1000==1; cp=(fr-frames[k-1][0])%1000==1
        dur=(tn-t0)*1000; span=(t1-t0)*1000; after=(tn-t)*1000; before=(t-t0)*1000
        body=[x[3] for x in L[f[1]:f[2]+1]]
        sig=[]
        for key in keys:
            c=sum(1 for b in body if key in b)
            if c: sig.append('%s=%d'%(key.strip(),c))
        back=[x for x in L[max(0,i-600):i] if t-x[1]<=0.6 and (' FOLLOWUP' in x[3] or 'DestroyDummy was from DestroyPerson' in x[3] or 'CumOn Clear' in x[3])]
        bk=';'.join('%s@-%dms(f%d)'%(('FOLLOWUP' if 'FOLLOWUP' in b[3] else ('DDfromDP' if 'DestroyDummy' in b[3] else 'CumOn')),(t-b[1])*1000,b[2]) for b in back[-3:])
        print('L%d f%d val=%s dur=%.0f span=%.0f before=%.0f after=%.0f contig(p,n)=%s,%s lines=%d | %s | back: %s'%(n,fr,tx.split(':')[-1].strip(),dur,span,before,after,cp,cn,len(body),' '.join(sig),bk))
        seq=L[f[1]:f[2]+1]+[L[nxt[1]]]
        for a,b in zip(seq,seq[1:]):
            g=(b[1]-a[1])*1000
            if g>=15: print('     gap %4.0fms L%d->L%d : %s'%(g,a[0],b[0],b[3][30:130]))
EOF

Script 2 : chemin, ancienne méthode (dtMESURE, qui est aussi la durée de n−1 selon la méthode corrigée), étendue n−1 et écart après l'impression, sur les deux journaux.

for f in M-H4-LifePlay.log J5-LifePlay.log; do echo "=== $f"; python -I - "$SP/$f" <<'EOF'
import re,sys
from datetime import datetime
ts=re.compile(r'^\[(\d{4})\.(\d\d)\.(\d\d)-(\d\d)\.(\d\d)\.(\d\d):(\d{3})\]\[\s*(\d+)\]')
L=[]
with open(sys.argv[1],encoding='utf-8',errors='replace') as fh:
    for n,line in enumerate(fh,1):
        m=ts.match(line)
        if not m: continue
        y,mo,d,h,mi,s,ms,fr=m.groups()
        L.append((n,datetime(int(y),int(mo),int(d),int(h),int(mi),int(s),int(ms)*1000).timestamp(),int(fr),line.rstrip('\n')))
F=[]
for i,x in enumerate(L):
    if not F or F[-1][0]!=x[2]: F.append([x[2],i,i])
    else: F[-1][2]=i
fi={}
for k,f in enumerate(F):
    for i in range(f[1],f[2]+1): fi[i]=k
rows=[]
for i,x in enumerate(L):
    if not ('GenerateRecs:' in x[3] and 'TopRight' in x[3]): continue
    k=fi[i]; f=F[k]; p=F[k-1]; nx=F[k+1]
    cp=(f[0]-p[0])%1000==1; cn=(nx[0]-f[0])%1000==1
    dtm=(L[f[1]][1]-L[p[1]][1])*1000 if cp else -1
    pspan=(L[p[2]][1]-L[p[1]][1])*1000
    nxt=L[i+1]; g=(nxt[1]-x[1])*1000; same=fi[i+1]==k
    fol=any(' FOLLOWUP' in y[3] and x[1]-y[1]<=0.6 for y in L[max(0,i-800):i])
    v=float(x[3].split(':')[-1]); hr=abs(v-round(v))<0.002
    path='C-finScene' if fol else ('B-heure?' if hr else 'autre')
    rows.append((path,dtm,pspan,g,same,cn,x[0]))
    print('%-10s L%-6d dtMESURE=%5.0f spanPrec=%5.0f apresPrint=%5.0f %s nextContig=%s -> %s'%(path,x[0],dtm,pspan,g,'memeTrame' if same else 'trameSuiv',cn,nxt[3][30:85]))
import statistics as st
for P in ('C-finScene','B-heure?','autre'):
    r=[x for x in rows if x[0]==P]
    if not r: continue
    print('%s : n=%d  dtMESURE>=50 : %d  spanPrec med=%.0f  apresPrint(memeTrame ou trame suivante contigue) med=%.0f max=%.0f'%(P,len(r),sum(1 for x in r if x[1]>=50),st.median([x[2] for x in r]),st.median([x[3] for x in r if x[4] or x[5]] or [0]),max([x[3] for x in r if x[4] or x[5]] or [0])))
print('total TopRight GenerateRecs',len(rows),' FOLLOWUP lignes',sum(1 for x in L if ' FOLLOWUP' in x[3]),' Found Home',sum(1 for x in L if 'Found Home' in x[3]))
EOF
done

Sorties résumées :

  • M-H4 : C n = 12, dont 12 à ≥ 50 ms selon l'ancienne méthode, étendue n−1 médiane 339 ms, après l'impression médiane 16 ms et maximum 225 ms ; B? n = 5, 0 ; autre n = 11, 1 ; total 28, 12 FOLLOWUP, 2 Found Home.
  • J5 : C n = 17, dont 13 ; étendue n−1 médiane 205 ms ; après l'impression médiane 15 ms et maximum 17 ms ; total 45, 17, 0.

Script 3 : répartition des écarts dans les trames qui contiennent FOLLOWUP (§ 3.5).

for f in M-H4-LifePlay.log J5-LifePlay.log; do echo "=== $f"; python -I - "$SP/$f" <<'EOF'
import re,sys
from datetime import datetime
from collections import defaultdict
ts=re.compile(r'^\[(\d{4})\.(\d\d)\.(\d\d)-(\d\d)\.(\d\d)\.(\d\d):(\d{3})\]\[\s*(\d+)\]')
L=[]
with open(sys.argv[1],encoding='utf-8',errors='replace') as fh:
    for n,line in enumerate(fh,1):
        m=ts.match(line)
        if not m: continue
        y,mo,d,h,mi,s,ms,fr=m.groups()
        L.append((n,datetime(int(y),int(mo),int(d),int(h),int(mi),int(s),int(ms)*1000).timestamp(),int(fr),line.rstrip('\n')[30:]))
F=[]
for i,x in enumerate(L):
    if not F or F[-1][0]!=x[2]: F.append([x[2],i,i])
    else: F[-1][2]=i
def norm(s):
    s=re.sub(r'_C_\d+|Person_C_\d+|\d+(\.\d+)?','#',s)
    s=re.sub(r'Actor\(.*','',s)
    return s[:70]
agg=defaultdict(float); cnt=defaultdict(int); tot=0; nfr=0; order=[]
for k,f in enumerate(F):
    body=L[f[1]:f[2]+1]
    if not any(' FOLLOWUP' in x[3] for x in body): continue
    nfr+=1
    span=(body[-1][1]-body[0][1])*1000; tot+=span
    fu=[j for j,x in enumerate(body) if ' FOLLOWUP' in x[3]][0]
    order.append('f%d span=%.0f FOLLOWUP a +%.0f ms (ligne %d/%d)'%(f[0],span,(body[fu][1]-body[0][1])*1000,fu+1,len(body)))
    for a,b in zip(body,body[1:]):
        g=(b[1]-a[1])*1000
        if g>=5:
            key=norm(a[3])+'  ==>  '+norm(b[3]); agg[key]+=g; cnt[key]+=1
print('trames de fin de scene (contenant FOLLOWUP):',nfr,' somme des etendues %.0f ms'%tot)
for o in order: print('  ',o)
print('ecarts >=5 ms agreges (avant ==> apres), top 14 :')
for kk,v in sorted(agg.items(),key=lambda x:-x[1])[:14]:
    print('  %6.0f ms  n=%2d  %s'%(v,cnt[kk],kk))
EOF
done

4. Verdict

  • H8 telle que formulée (« GenerateRecs cause les saccades de fin de scène ») : réfutée (établi par l'ordre des lignes et la durée de chaque trame, § 3.4).

    • Le temps de la fin de scène est dans la trame n−1 : 261 à 807 ms dans M-H4, 144 à 331 ms dans J5.
    • GenerateRecs tourne ensuite, dans une trame de 18 à 56 ms (8 cas bornés sur 12 ; J5 : 17 ms au plus après l'impression).
    • Sa position « juste après » vient du Delay de 0,2 s de DelayThenFollowUp (DelayAction.h:29-30).
  • Volet a, fin de scène : cause localisée dans la trame de fin de scène (n−1) (établi pour la répartition entre lignes, estimé pour les fonctions, § 3.5). Le temps se partage entre :

    • deux blocs de bascule d'interface et de mode d'entrée : environ 70 ms chacun dans M-H4, 32 ms dans J5 ;
    • les modifications de statistiques : 25 à 35 ms chacune ;
    • le rhabillage par vêtement ;
    • CumOn Clear ;
    • DestroyDummy.

    Alléger ou supprimer GenerateRecs ferait gagner au plus 15 à 56 ms par fin de scène (estimé), sans effet visible sur la saccade.

  • Volet b, trames GenerateRecs :

    • établi : 6 des 8 trames longues (1 823 ms sur 2 107) sont des arrivées dans un lieu, sans fin de scène ;
    • indécis : à l'arrivée, ce qui coûte est soit GenerateRecs (au moins 90 ms dans les deux cas Found Home, estimé), soit le code qui le suit (TopRight.Current) ; c'est vraisemblablement dans un rappel de minuterie (1 410 ms de TimerManager rapportés).
    • Les dumps manquants (§ 5.4) trancheront.

5. Hypothèse suivante proposée, et à quel niveau

5.1 H9 (proposée) : la trame de fin de scène (volet a)

Hypothèse. La trame qui contient FOLLOWUP, DestroyDummy et CumOn Clear dure 140 à 800 ms à cause de la chaîne de fin de scène du GI :

  • la bascule de mode d'entrée et d'interface : deux écarts de 30 à 70 ms entre les changements HideCursorDuringCapture ;
  • les modifications de statistiques : environ 25 à 35 ms chacune ;
  • le rhabillage Dress : environ 6 à 15 ms par vêtement ;
  • CumOn Clear : environ 24 à 40 ms ;
  • DestroyDummy.

Niveau : N3, lp-cart, DIAG, sur dumps. Points d'entrée :

  • les appelants de DelayThenFollowUp : K2Node_CallFunction_167 et 168, depuis IfThenElse_1563 et 1562.else (V5 l. 18760-18765) ;
  • dans Person : ModifyStat, Dress et CumOn ;
  • les fonctions de PC qui changent le mode d'entrée.

Ensuite, une mesure avec -LPHitchLog (Q-H8-4).

5.2 H8b (proposée) : GenerateRecs à l'arrivée dans un lieu (volet b)

Partager les 90 à 313 ms entre GenerateRecs, recommendActions et TopRight.Current, et recaler la part TimerManager sur les trames du journal. Niveau : N3, lp-cart, DIAG, après STE-D9.

5.3 Correctifs comparés (Q-c), sans choix

Les nœuds ne sont nommés que du côté GI. Ceux de GenerateRecs attendent le dump.

Id Nature Où Effet attendu (estimé) Effet visible pour le joueur Risque
C0 Ne rien changer à GenerateRecs pour la fin de scène ; traiter la trame n−1 (H9) — En fin de scène, tout le gain viendra de n−1 (médiane 339 ms d'étendue dans M-H4, 205 ms dans J5) Dépend de H9 Aucun
C1 Ne recalculer que si nécessaire (garde ou cache) GI, avant les appels 302, 312 et 313 (V5 l. 19973, 20010, 20017) : garde sur (heure, lieu, CurrentActionDes, statistiques), comme LastHourRec le fait déjà pour 312 (l. 19994-20000) À l'arrivée : jusqu'à 90-313 ms (M-H4) ou 82-118 ms (J5) quand rien n'a changé. En fin de scène : 56 ms au plus Arrivée plus fluide quand l'état ne change pas ; rien en fin de scène Recommandations périmées si la garde oublie une dépendance (besoins, statistiques, quête)
C2 Différer Appel au tick suivant (minuterie à 0 s ou Delay court) pour 302 et 312 ; 313 est déjà différé (Delay 0,2) Le coût quitte la trame d'arrivée (198 à 445 ms) mais crée plus tard une trame de 90 à 313 ms Saccade déplacée, pas supprimée ; panneau mis à jour une trame plus tard Appels qui se croisent (ClearRecs 306 et 307 pendant le calcul)
C3 Étaler sur plusieurs trames Découper : recommendHome / Job / BasicNeeds / Companion, puis la création des widgets Chaque étape coûte environ 1/4 à 1/5 du total, soit 20 à 80 ms Panneau qui se remplit en quelques trames Ordre et doublons des recommandations ; davantage de nœuds BP à modifier
C4 Alléger en natif Fonction C++ (module LifePlay) pour SortByDistance, SearchByAmenitySafe et FindCenterOfPolygon, branchée par MCP (lp-mcp) Gain fort attendu (boucles BP remplacées par du natif), non chiffrable sans dump Arrivée plus fluide Équivalence du tri à prouver ; C++, recompilation et rebranchement BP
C5 Mettre en cache Centres des bâtiments et résultats de recherche par type de lieu, calculés une fois par carte Supprime les recalculs de centres (part inconnue) Comme C4, en plus faible Invalidation si la carte change

5.4 Dumps manquants (lot STE-D9) : arrêt sur ce point, aucun commandlet lancé

  • LPDumpFunction accepte un Widget Blueprint (établi) :
    • la recherche porte sur UBlueprint et ses sous-classes (GetAssetsByClass(..., true), LPDumpFunctionCommandlet.cpp:106) ;
    • class UWidgetBlueprint : public UBaseWidgetBlueprint (UE_5.8\...\UMGEditor\Public\WidgetBlueprint.h:218) ;
    • précédent : etat\portage55\L4-CX-B04\L4-CX-B04_dump_avant_TopRight_EventGraph.txt:1 (=== TopRight :: EventGraph (907 noeuds) ===).
  • Le commandlet lit sans sauvegarder (LPDumpFunctionCommandlet.cpp:81-198). Sans -Fn, il liste les graphes (l. 135-145).
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=TopRight'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=GenerateRecs -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=SortByDistance -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=TopRight -Fn=Current -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendActions -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendKeyActions -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendHomeIfNeeded -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendJobIfNeeded -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendBasicNeeds -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=Person -Fn=recommendCompanion -Pins -Exec'
.\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=StreetMap_Static -Fn=SearchByAmenitySafe -Pins -Exec'

Current est le nom vu dans les appels du GI (STB-GIA:21699). S'il n'apparaît pas dans la liste de la première commande (cas d'un événement personnalisé), utiliser -Fn=EventGraph. Pour H9, il faudra aussi Person ModifyStat, Dress et CumOn, ainsi que les fonctions de PC qui changent le mode d'entrée ; leurs noms exacts sont à lire dans la liste des graphes.

5.5 Questions pour l'utilisateur

  • Q-H8-1 : acceptez-vous la réfutation de H8 telle que formulée ? Si oui, faut-il ouvrir H9 (trame de fin de scène n−1, § 5.1) en N3, DIAG lp-cart ?
  • Q-H8-2 : le coût de GenerateRecs à l'arrivée dans un lieu (6 des 8 trames longues, 1 823 ms ; 90 à 313 ms après l'impression) doit-il avoir sa fiche (H8b) maintenant, après H9, ou pas du tout ?
  • Q-H8-3 : faut-il lancer le lot STE-D9 (§ 5.4), éditeur fermé, et par qui (orchestrateur ou lp-builder) ?
  • Q-H8-4 : faut-il refaire une mesure avec -LPHitchLog, sans -llm -llmcsv ?
    • Cette option émet sa ligne en fin de trame, avec la durée de la trame elle-même (LPDebugSubsystem.cpp:396-417, seuil -LPHitchMs=, 50 ms par défaut, l. 339-340).
    • Faut-il aussi rebrancher, pour étiqueter les chemins, les trois PrintString déjà présents (309, 314, 315) ? Ce serait une modification BP par MCP, avec instantané.
  • Q-H8-5 : faut-il réécrire STE-H4-MESURE.md § 4-5 avec le recompte corrigé ? La classe « GenerateRecs » y est donnée comme « juste après une fin de scène », alors que 6 trames sur 8 sont des arrivées dans un lieu.
  • Q-H8-6 : si vous voulez un correctif de GenerateRecs, lequel retenir (C1 à C5) ? L'ordre des recommandations doit-il rester identique ?

Tours : 37 / 60

Constats

  • Écarts à la fiche :
    • HEAD vaut c01b543 (U8, après 4283c13), et non 4283c13. Pour TopRight, GI et Person, git log -1 donne 4283c13 (GI) et des commits plus anciens. git show --stat c01b543 -- Content ne liste rien.
    • Les analyses ont été faites par python -I - sur l'entrée standard (aucun fichier écrit), et non par mesure_h4.py, dont elles reprennent le regroupement par trame.
    • Le journal J5 a servi de second échantillon.
    • Une partie des nœuds provient de dumps d'autres lots, cités avec leur date : STB-GIA du 2026-10-09 ; dumps 5.5 de Person (L6f) et de TopRight (L4).
    • Le livrable a été réécrit une fois en entier, après la correction de l'orchestrateur : deux volets et recompte corrigé.
  • Correction de l'orchestrateur (décalage d'une trame) : je l'avais relevée aussi (mesure_h4.py:88-93). Elle est intégrée aux § 1, 3.4 et 3.6. Ses 8 trames GenerateRecs (2,1 s) sont retrouvées à l'identique (2 107 ms). Ses 2 230 ms et 1 410 ms de TimerManager ne sont pas rejoués.
  • Pièges rencontrés (zéro résultat ne prouve pas une absence) :
    • Grep GenerateRecs sur le dossier etat\ST\STE\dumps → 0, alors que le fichier GI__EventGraph_V5.txt passé seul → 6 lignes (l. 19962-20017) ;
    • Grep sur Content avec *.uasset → 0, alors que TopRight.uasset seul → 8.
  • FOLLOWUP est imprimé aussi à l'écran (bPrintToScreen = 'true', V5 l. 25855), à chaque fin de scène, dans les builds où PrintString est actif.
  • Fichiers écrits : D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-H8-DIAG.md (LF), seul fichier.
  • Fichiers à commiter (dépôt du jeu, branche ue5.8) : A Docs/Migration/etat/ST/STE/STE-H8-DIAG.md.
  • Suite : l'orchestrateur porte Q-H8-1 à Q-H8-6 à l'utilisateur. STE-D9 n'est à lancer que sur décision. État : DIAG terminé, H8 réfutée telle que formulée, volet a localisé dans la trame n−1, volet b indécis, H9 et H8b proposées.