39 KiB
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.
GenerateRecsne tourne qu'à la trame suivante, déclenché par unDelayde 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 entreGenerateRecset le code qui le suit reste indécis faute de dump.
1. Symptôme et reproduction
-
Symptôme initial :
STE-H4-MESURE.md:39compte 13 trames, 4,9 s, classe «GenerateRecs(TopRight, juste après une fin de scène) ».STE-H4-MESURE.md:50-52en 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
GenerateRecsfont 2 230 ms, dont 1 410 ms deTimerManager.
- fin de scène (DESTROY,
-
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). GrepGenerateRecs→ 30 lignes :- 28 lignes
[TopRight_C_2147482028] GenerateRecs: <valeur>(l. 1258 à 24913) ; - 2 lignes
[PlayerCharacter] GenerateRecs: Found Home(l. 1259 et 1758).
- 28 lignes
-
Second échantillon :
...\scratchpad\J5-LifePlay.log(45 lignes TopRight, 17 lignesFOLLOWUP, 0Found 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 contiennentGenerateRecs(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 (colonnedurdu 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/TimerManagerexiste dans les captures CSV (établi, Grep : l. 1 et 9002 de...\STE-J5_Development\Windows\LifePlay\Saved\Profiling\CSV\Profile(20261011_012223).csvet(…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
GenerateRecssont-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
PrintStringqui é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
Delay119 (VariableSet_278etIfThenElse_140.else) sont mortes (établi) :IfThenElse_140a une entrée d'exécution vide (l. 25153-25157) ;277→278ne part que de140.then(l. 25158-25166).
-
Autres assets. Grep
GenerateRecssur chaque fichier passé explicitement :Asset Occurrences Remarque TopRight.uasset8 GI.uasset2 Person.uasset2 texte Found HomeBPs\PC,BPs\Room,StreetMap_Static,UI\Cursor0 Le reste de
Contentn'a pas été cherché : un Grep sur un dossier saute les binaires (Contentavec*.uasset→ 0, alors queTopRight.uassetseul → 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.uassetcontientK2_SetTimerDelegateetK2_ClearAndInvalidateTimerHandledans 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.
- le chemin C passe par un
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. GenerateRecstourne 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'unGenerateRecsdans la trame suivante ; - J5 : 17 lignes et 17 appels.
- M-H4 : 12 lignes
- 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.
- dans la même trame, l'impression est suivie de
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 dansTopRight.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 :
GenerateRecsvideRecBox, fait calculer les recommandations par le personnage joueur, trie des bâtiments par distance, puis crée un widgetRecommendpar entrée.
- noms trouvés :
- 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) appellerecommendKeyActions(9017),GI.CheckCondition(9113),GI.getAmenitiesForAction(9237),StreetMap.isAmenityOpen(9474, 9587),StreetMap.SearchByAmenitySafe(9688) etStreetMap.CalculateDistanceSafe(9711).recommendKeyActions(l. 9724) appellerecommendHomeIfNeeded,recommendJobIfNeeded,recommendBasicNeedsetrecommendCompanion(9743-9755).- Ces quatre fonctions appellent à leur tour
StreetMap.SearchByAmenitySafe(9926, 10137, 10658, 10664),TopRight.SortByDistance(9842, 9979, 10167, 10519) etGI.getActionDetail(6 appels, l. 10111 à 10551). - Le texte
GenerateRecs: Found Homeest dansrecommendHomeIfNeeded(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
FOLLOWUPet 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 ;
GenerateRecss'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) ;GenerateRecss'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,DestroyDummychez l'orchestrateur ;FOLLOWUPici. - Part de
GenerateRecsaprè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).
- l. 12827 : 228 ms, qui se terminent sur une ligne
- 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
GenerateRecsest 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
GenerateRecstombe juste après la trame longue.- Établi :
DelayAction.h:29-30(UE 5.8) retireResponse.ElapsedTime()du temps restant et déclenche dès qu'il est ≤ 0. - Établi :
GenerateRecsse déclenche dans n alors que 141 à 184 ms seulement se sont écoulés depuisFOLLOWUPdans 9 cas sur 12, soit moins que les 0,2 s. LePrintString826 et leDelay119 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 ;
LatentActionManagern'a pas été relu. - Conclusion : la coïncidence vient du
Delayet de la longueur de n−1, pas du coût deGenerateRecs.
- Établi :
- 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,
DressetCumOn. - Ces quatre postes font l'essentiel des 256 à 797 ms. La ligne
FOLLOWUParrive 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).
- l. 1258 → 1259 (
-
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 Homeimprimé parrecommendHomeIfNeeded. Si la chaîneGenerateRecs→recommendActions→recommendKeyActions→recommendHomeIfNeededest la bonne (estimé, § 3.3), au moins 90 ms sont dansGenerateRecs. - Pour les autres arrivées, l'écart court jusqu'à
CHECK FOR clOSE <lieu>. Il couvre aussi le code qui suitGenerateRecs, par exempleTopRight.Currentau chemin A (estimé). Le partage est indécis. - Les 1 410 ms de
TimerManagerrapporté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
SearchByAmenitySafesur tous les bâtiments d'un type, trisSortByDistanceavec unFindCenterOfPolygonpar bâtiment,CheckConditionpar action, création des widgetsRecommend. - Postes écartés : ces trames ne contiennent pas de lecture
.lpsceneni deCHECKING:(ces lignes sont réparties à raison d'une par trame, par exemple l. 4812-4829).
- Dans les l. 1257 → 1258 et 1757 → 1758, 90 et 96 ms séparent l'impression de TopRight du
-
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 deGenerateRecstranchera. -
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.
- 30 lignes au total : 28 de TopRight et 2 de
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, 2Found 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 («
GenerateRecscause 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.
GenerateRecstourne 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
Delayde 0,2 s deDelayThenFollowUp(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
GenerateRecsferait 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 casFound Home, estimé), soit le code qui le suit (TopRight.Current) ; c'est vraisemblablement dans un rappel de minuterie (1 410 ms deTimerManagerrapporté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_167et168, depuisIfThenElse_1563et1562.else(V5 l. 18760-18765) ; - dans Person :
ModifyStat,DressetCumOn; - 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é
LPDumpFunctionaccepte un Widget Blueprint (établi) :- la recherche porte sur
UBlueprintet 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) ===).
- la recherche porte sur
- 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
PrintStringdéjà présents (309, 314, 315) ? Ce serait une modification BP par MCP, avec instantané.
- Cette option émet sa ligne en fin de trame, avec la durée de la trame elle-même (
- 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ès4283c13), et non4283c13. PourTopRight,GIetPerson,git log -1donne4283c13(GI) et des commits plus anciens.git show --stat c01b543 -- Contentne liste rien. - Les analyses ont été faites par
python -I -sur l'entrée standard (aucun fichier écrit), et non parmesure_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é.
- HEAD vaut
- 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 tramesGenerateRecs(2,1 s) sont retrouvées à l'identique (2 107 ms). Ses 2 230 ms et 1 410 ms deTimerManagerne sont pas rejoués. - Pièges rencontrés (zéro résultat ne prouve pas une absence) :
- Grep
GenerateRecssur le dossieretat\ST\STE\dumps→ 0, alors que le fichierGI__EventGraph_V5.txtpassé seul → 6 lignes (l. 19962-20017) ; - Grep sur
Contentavec*.uasset→ 0, alors queTopRight.uassetseul → 8.
- Grep
FOLLOWUPest imprimé aussi à l'écran (bPrintToScreen = 'true', V5 l. 25855), à chaque fin de scène, dans les builds oùPrintStringest 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.