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

20 KiB
Raw Blame History

Fiche STE-H7 — 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é, non utilisé ; lp-doc (gabarits)

STE-H7-DIAG : chargements synchrones (FlushAsyncLoading) à l'apparition des PNJ, coût et voies comparées (2026-10-11)

Résultat : hypothèse réfutée comme cause principale. Les FlushAsyncLoading (FAL) existent au SPAWN, mais ils ne pèsent que 5 % du temps des trames SPAWN. Le reste vient surtout du Blueprint relancé par les actions latentes (FlushLatentActions) : 64 % dans la capture 0, 78 % dans la capture 1. Chiffres de la capture 0 : 41 trames SPAWN, 5 753 ms au total, médiane 123 ms, dont 301 ms de FAL et 3 656 ms de FlushLatentActions. Un chargement synchrone coûte 4,2 ms en médiane (max 26,9 ms). Les FAL des vêtements (DressSavedActual → GI getSkeletalMeshFromPath) ne donnent pas de saccade : sur 75 trames, médiane 17 ms, 2 seulement ≥ 50 ms. Les « 21 FAL seuls » de STE-H4-MESURE viennent d'un décalage d'une trame dans la méthode. Corrigée, la capture 0 n'a que 2 trames longues dominées par un FAL (408 ms, chargement initial) et la capture 1 aucune. Les noms des paquets chargés ne figurent dans aucune source disponible : il faut une mesure et 5 dumps (§ 5). Aucun éditeur, commandlet, build ni jeu n'a été lancé ; seul ce fichier est écrit.

Copie D:\Game\LifePlay-master 5.8, HEAD 4283c13 (fiche, rubrique 3). Abréviations (chemins absolus) :

  • M = %USERPROFILE%\AppData\Local\Temp\claude\D--Game-LifePlay-master-5-8\37530d8d-c780-4a57-8fbd-bcb010aa6408\scratchpad\M-H4-LifePlay.log (22 488 lignes horodatées).
  • CSV0 / CSV1 = D:\Game\LifePlay_Build_5.8_STE-J5_Development\Windows\LifePlay\Saved\Profiling\CSV\Profile(20261011_012223).csv / Profile(20261011_012509).csv (9 000 trames chacune ; en-tête en ligne 1 et en fin de fichier).
  • Dumps D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\dumps\ : PEG4 = Person__EventGraph_D4.txt, LMIN = GI__LoadMeshIfNeeded_D4.txt, RM5 = Room__EventGraph_V5.txt.
  • UE = D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\ (moteur installé, lu).

1. Symptôme et reproduction

  • Symptôme (STE-H4-MESURE.md:38-40, 45) : 46 trames SPAWN ≥ 50 ms (5,5 s), dont 38 avec FAL ; 21 trames « FAL seul » (4,5 s) ; 422 FAL dans le journal. Jeu non relancé ; relecture de M, CSV0 et CSV1.
  • Recompte (Grep FlushAsyncLoading, count, sur M) : 422 lignes, toutes LogStreaming: Display:. Files d'attente : 418 × 1 QueuedPackages, 0 AsyncPackages, 2 × 0, 0, 1 × 8, 0 (M:806), 1 × 0, 1. Identifiants entre parenthèses : 8 → 649, strictement croissants.
  • AsyncLoadRequests_2026.10.11-01.25.09.log:1 et …01.27.47.log:1 : « Captured 0 unique callstacks » (fichiers vides).
  • Captures CSV dans M : début M:966 (trame [1]) et M:11330 (trame [3]), fin M:11313 et M:18504. La 3e capture (M:18511) est interrompue et écartée (STE-H4-MESURE.md:14).

2. Hypothèse testée (une seule)

H7 : les trames SPAWN longues et les trames « FAL seul » viennent des chargements synchrones d'assets de personnage (maillages, vêtements) déclenchés sur le chemin du SPAWN. Réfutée si le temps passé dans FlushAsyncLoading ne fait qu'une petite part de la durée de ces trames.

3. Essai et résultat brut

Méthode : 7 passages Python en lecture seule (Constats). Chaque ligne de M est rattachée à une ligne CSV selon le numéro de trame modulo 1000 et le temps écoulé depuis le début de la capture, puis lue avec les colonnes Exclusive/GameThread/*.

3.1 Ce que dit une ligne FAL (établi, moteur)

  • La ligne est écrite avant l'attente (UE CoreUObject\Private\Serialization\AsyncPackageLoader.cpp:297 puis FlushLoading :306), seulement si IsAsyncLoading() && IsInGameThread() (:290). Elle n'est en Display qu'une fois par trame ; les suivantes de la même trame sont en Log (:295-301). Or LogStreaming est par défaut en Display (UE Core\Public\CoreGlobals.h:43) : le journal ne montre que le premier FAL de chaque trame.
  • Chaque appel ajoute un événement CSV (CSV_EVENT_GLOBAL_NOLOG, :279) et compte son temps dans Exclusive/GameThread/FlushAsyncLoading (:278).
  • Un chargement synchrone (LoadObject, LoadSynchronous, Load Asset Blocking…) appelle LoadPackageAsync, puis FlushAsyncLoading(RequestID) (UE CoreUObject\Private\UObject\UObjectGlobals.cpp:1759-1764). Le nombre entre parenthèses est donc l'identifiant de la demande. 1 QueuedPackages (418 lignes sur 422) = un paquet racine par chargement.
  • Aucune source disponible ne nomme les paquets. La ligne FAL ne porte pas de nom. Le traceur TrackAsyncLoadRequests note le nom de chaque paquet demandé (TrackRequest(InPackagePath.GetDebugName(), …), AsyncPackageLoader.cpp:599 ; actif hors Shipping et Test, :28) et l'écrit à la fin d'une capture CSV (:398-401). Mais il est désactivé par défaut (TrackAsyncLoadRequests.Enable = 0, :365-367 ; retour immédiat :488) : d'où les fichiers vides.

3.2 Alignement journal ↔ CSV (établi)

  • Le décalage 0 donne 215 lignes CSV0 sur 215 et 122 lignes CSV1 sur 122 : chaque trame qui porte un FAL dans M porte aussi un événement FAL dans le CSV.
  • FrameTime de la ligne r mesure la trame r − 1 : la valeur est prise au début de la trame, juste après UpdateTimeAndHandleMaxTickRate (UE Core\Private\ProfilingDebugging\CsvProfiler.cpp:4369-4388). Contrôle sur les 68 lignes ≥ 50 ms de CSV0 : Σ|FT[r] − GameThreadTime[r − 1]| = 378 ms, contre 11 380 ms avec GT[r]. CSV1 : 356 contre 5 564 ms. Une trame longue r se lit donc en FT[r + 1], avec les colonnes exclusives et le contenu du journal de la ligne r.
  • STE-H4-MESURE.md § 4 attribue la durée de la trame i − 1 au contenu de la trame i. Son script mesure t(1re ligne de i) − t(1re ligne de i − 1) et classe par les lignes de i (scratchpad\mesure_h4.py:88-108). Le décalage est le même que celui de FrameTime, et :52 teste aussi FT sur la ligne de l'événement. Contrôle : les 34 trames SPAWN longues de cette méthode situées dans les captures ont toutes FT[i] ≥ 50, mais seulement 23 ont FT[i − 1] ≥ 50.
  • CSV1 n'a pas de colonne Exclusive/GameThread/FlushAsyncLoading (513 colonnes, 131 événements FAL). Le temps des FAL n'est donc mesuré que dans CSV0. Cause : incertaine.

3.3 Q-a : ce qui déclenche les FAL

Contexte des 422 FAL. Classe = la dernière ligne Blueprint écrite avant le FAL dans la même trame :

Contexte FAL Écart médian jusqu'à la ligne suivante Temps FAL dans CSV0
DressSavedActual (test19) 147 38 ms 557 ms
index SPAWN 40 25 ms 221 ms
CHECKING: (voisinage) 50 16 ms 166 ms
aucune ligne Blueprint avant, dans la trame 183 49 ms 597 ms
autre (stop getting npcs, inside box) 2 15 ms 18 ms
  • Vêtements (chaîne établie, nœud de chargement estimé fort). Dans Person, l'événement DressSavedActualActual (K2Node_CustomEvent_23, PEG4:10550) est réarmé par K2_SetTimerDelegate (_142, PEG4:10553-10561). La chaîne : PrintString 'DressSavedActual (test19)' (_231, PEG4:10535-10540) → GI.getSkeletalMeshFromPath (_190, PEG4:10519-10524), dont le Path vient de SavedClothes[0] → Cloth.Base.SkeletalMeshPath (GetArrayItem_4, BreakStruct_7, _8, PEG4:10509-10533, 10546-10549) → currentSM. Dans le journal, le FAL suit l'impression dans la même milliseconde (M:1614-1615, 1625-1626, 1653-1654, 1662-1663, 1669-1670). Le paquet est le vêtement nommé ensuite par Dressed <article> pour le même acteur (ex. M:1625-1626 → M:1659 Office-Attire-Skirt_1_F ; estimé). Le contenu de getSkeletalMeshFromPath (nœud de chargement exact) n'est pas dumpé.

  • SPAWN (nœud non établi). Chaîne : Room GenerateUnknownNpcs (RM5:2254) → GI GeneratePerson (RM5:2260) → index SPAWN → Person RandomizeAll (pile de script M:1563-1578). Le FAL tombe entre index SPAWN et wear glasses (test2206) (M:1589-1591, 1598-1600, 1606-1608). Cette impression n'est ni dans PEG4 ni dans RM5 (Grep test2206 → 0 sur les deux). Candidats de GI, tous à cache « IfNeeded » :

    • LoadMeshIfNeeded : IsValid(MeshSexRace[i].Mesh) ; branche Is Not Valid → getSkeletalMeshFromPath → Array_Set (cache du corps, LMIN:140-157, 193-196), seulement si race Daz, LegacyPercentage > 0, Dog, Horse ou Tentacles (LMIN:171-235) ;
    • LoadHairMeshIfNeeded sur Is Not Valid (PEG4:9244-9248, 9587-9591) ;
    • LoadOtherMeshIfNeeded_0 dans un ForEach (PEG4:9483-9489) ;
    • LoadDefaultTextures (PEG4:17892).

    Dumps requis (§ 5).

  • 183 FAL sans ligne Blueprint avant. Ce sont des chaînes, un FAL toutes les 3 trames après un LOAD ROOM : M:1285-1322 (17 FAL après M:1271), M:3038-3060 (12 FAL). Elles voisinent avec Meubles assis ou couches (LPShowCoupleSubsystem.cpp:468) et des lignes F5. OpenUseFurniture ne charge rien : seulement DoesPackageExist (LPShowCoupleSubsystem.cpp:405-470, :425). Chaque FAL de ces chaînes coûte de 1 à 19 ms dans des trames de 17 ms (CSV0) : pas de saccade. Source : incertaine (mobilier du lieu, estimé).

  • C++ (établi, Grep LoadObject|StaticLoadObject|LoadSynchronous|TryLoad\(|LoadPackage|… sur Source). Chargements synchrones en jeu :

    • LPModDailies.cpp:126 : une seule fois, si Loaded.Num() == 0 (:122-135) ;
    • LPShowCoupleSubsystem.cpp:178 : paire d'animations de scène ;
    • LPDirectControlSubsystem.cpp:961 ;
    • LPSettingsSubsystem.cpp:474.

    Aucun n'est sur le chemin du SPAWN (estimé). Plugins de la copie : Grep des mêmes motifs sur Plugins\*.cpp → 0 ; ModdingExtraBPLibrary.h:31-50 ne lit que des fichiers texte.

3.4 Q-b : coût (établi sur CSV0, sauf mention)

  • Total des FAL dans CSV0 : 1 559 ms sur 165,5 s, soit 0,94 % (344 événements sur 215 lignes). Par chargement : médiane 4,2 ms, p90 10,7 ms, max 26,9 ms.
  • Trames ≥ 50 ms, avec la correction d'une trame (§ 3.2), classées par le contenu de la trame mesurée. Colonnes en ms :
CSV0 Trames Durée GameThread FlushLatentActions FAL TimerManager
SPAWN 35 5 616 5 552 3 560 301 195
autre (dont démarrage) 10 5 185 5 094 389 0 360
GenerateRecs (H8) 8 2 230 2 201 1 16 1 410
CHECKING: 8 814 791 417 0 111
FAL sans autre marqueur 2 408 389 65 312 0
DESTROY 1 290 276 0 0 0
DressSavedActual 2 163 165 0 6 140
écriture LLM / sans ligne 2 143 92 0 0 67
Total 68 14 849 635 (4,3 %)

CSV1 (pas de colonne FAL) : SPAWN 16 trames, 2 377 ms, FlushLatentActions 1 858 ms (78 %) ; autre 31 trames, 3 263 ms ; DESTROY 5, 1 464 ms ; sans ligne 10, 808 ms ; GenerateRecs 1, 240 ms ; LLM 1, 97 ms. Aucune trame longue « FAL seul ».

  • Par SPAWN. Les 41 trames SPAWN de CSV0 durent 5 753 ms au total (médiane 123 ms). Elles contiennent FlushLatentActions 3 656 ms (64 %), FAL 301 ms (5,2 %) et GameThread 5 687 ms ; 40 événements FAL, soit ≈ 7,3 ms par trame SPAWN. CSV1 : 24 trames, 2 563 ms, médiane 115 ms, FlushLatentActions 1 986 ms (77 %). FlushLatentActions = traitement des actions latentes restantes (UE Engine\Private\LevelTick.cpp:1795-1797) ; le Room a des Delay (RM5:270, 420). Estimé : la chaîne du SPAWN (GeneratePerson, RandomizeAll, LoadPreset…) reprend après un Delay. Son Blueprint est alors compté sous ce nom, hors portées plus fines comme FAL.
  • Premiers SPAWN contre suivants. 95 index SPAWN, 6 acteurs seulement (mannequins recyclés). Part avec un FAL dans la même trame : 4 sur 6 à la première apparition, 51 sur 89 aux reprises (57 %). Pas d'effet de cache visible. Estimé : l'asset diffère d'un PNJ à l'autre, ou il est libéré entre deux usages.
  • Vêtements. 239 DressSavedActual, dont 147 suivis d'un FAL ; 145 articles distincts (appariés à la ligne Dressed suivante du même acteur, 0 non apparié). FAL à la première pose : 121 sur 145 ; aux poses suivantes : 26 sur 94. Cache partiel (estimé : l'asset reste chargé tant qu'il est référencé). Coût : 75 trames dans CSV0, médiane 17 ms, 2 seulement ≥ 50 ms ; 477 ms de FAL au total, absorbés par la marge (GameThread médian 5,4 ms, STE-H4-MESURE.md:22).
  • Les « 21 FAL seuls » de STE-H4-MESURE. 6 tombent hors des captures. Sur les 14 restantes, après correction, il ne reste que les 2 trames de CSV0 (408 ms, dont 312 ms de FAL). L'une est la ligne 44 : 87 événements FAL, 306 ms, journal M:996 à 23:22:24, chargement initial, avant le premier LOAD ROOM (M:1246). Les autres sont les trames qui suivent une trame longue d'une autre classe (décalage du § 3.2).
  • -llmcsv : l'écriture LLM coûte 75 ms dans les trames SPAWN longues de CSV0 (colonne Exclusive/GameThread/LLM) : effet faible.

3.5 Q-c : correctifs comparés (sans décision)

Effets chiffrés sur CSV0 (estimés, sauf les coûts actuels, établis au § 3.4).

Voie Où (nœuds, fichiers) Effet attendu sur FAL Effet sur les saccades Mémoire Risque principal
P1 Précharger à l'entrée du lieu Room, au LOAD ROOM : chargement asynchrone des chemins connus (vêtements SavedClothes, cheveux, corps) par le nœud latent Async Load Asset dans l'EventGraph, ou un sous-système C++ à créer dans Source\LifePlay (FStreamableManager::RequestAsyncLoad, poignées gardées jusqu'au SPAWN) ; SPAWN après la fin du chargement SPAWN −301 ms (−40 FAL), vêtements −477 ms trames SPAWN −5 % (médiane ≈ 123 → ≈ 116 ms), toujours ≥ 50 ms mêmes assets, chargés plus tôt ; poignées à libérer PNJ inconnus tirés au hasard au SPAWN : rien à précharger sans réordonner (tirer, charger, puis faire apparaître) ; logique Blueprint plus lourde
P2 Asynchrone sur place (vêtements) Person DressSavedActualActual : remplacer l'appel bloquant GI.getSkeletalMeshFromPath (_190, PEG4:10519) par Async Load Asset (permis dans un événement), puis currentSM à la fin vêtements −147 FAL (−477 ms) ≈ nul : 2 trames longues sur 75, 6 ms de FAL inchangée l'acteur recyclé (ResetVars) avant la fin du chargement : garde IsValid ou compteur de génération ; vêtement visible 1 à 2 trames plus tard
P3 Garder une référence (cache) GI : table chemin → SkeletalMesh alimentée par getSkeletalMeshFromPath, comme MeshSexRace pour le corps (LMIN:140-157) reprises seulement : vêtements ≤ −26 FAL ; SPAWN ≤ −51 FAL si l'asset passe par la même fonction (inconnu) SPAWN ≤ −5 % croît avec les articles distincts (145 dans la session ; maillages 70-80 Mo au total, STE-H4-MESURE.md:30) : plafond ou vidage au changement de carte à prévoir ; non chiffré croissance mémoire ; vidage à définir
Abstention sur FAL — 0 0 0 aucun ; le coût principal reste dans FlushLatentActions (§ 5, H7b)

Le temps visé par la fiche (≈ 10 s) n'est pas du FAL. Dans CSV0, les FAL font 635 ms sur 14 849 ms de trames longues. Aucune des voies P1-P3 ne fait passer une trame SPAWN sous 50 ms (estimé, médiane 123 ms).

4. Verdict : réfutée (cause principale) ; FAL présent mais minoritaire

  • Établi :
    • une ligne FAL = premier chargement synchrone de la trame, écrit avant l'attente, sans nom de paquet (§ 3.1) ;
    • FrameTime[r] = durée de la trame r − 1, et la méthode de STE-H4-MESURE § 4 porte le même décalage (§ 3.2) ;
    • FAL = 5,2 % des trames SPAWN et 4,3 % des trames longues (CSV0), FlushLatentActions = 64 % des SPAWN (CSV0) et 77 % (CSV1) ;
    • chaîne des vêtements jusqu'à getSkeletalMeshFromPath ; 147 FAL sur 239 DressSavedActual ; trames de vêtements non longues ;
    • « FAL seul » long : 2 trames dans CSV0 (chargement initial), 0 dans CSV1.
  • Estimé : le FAL des vêtements a lieu dans getSkeletalMeshFromPath ; le Blueprint du SPAWN est relancé par un Delay du Room ; il n'y a pas de cache efficace au SPAWN (57 % des reprises rechargent) ; effets des voies P1-P3.
  • Incertain : le nœud et le paquet du FAL du SPAWN ; la source des chaînes de FAL après LOAD ROOM ; l'absence de la colonne FAL dans CSV1 ; les fonctions Blueprint qui font les 3,6 s de FlushLatentActions.

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

  • H7b (N3, lp-cart, puis mesure) : le coût des trames SPAWN vient du Blueprint relancé par actions latentes (GeneratePerson, RandomizeAll, LoadPreset, morphs, matériaux, PlayDaily), pas des chargements. Il faudrait le coût par fonction Blueprint (Unreal Insights ; que les noms des fonctions Blueprint y figurent est incertain) et les dumps ci-dessous.
  • Dumps manquants (lot STE-D9), arrêt sur ce point pour le nœud et le paquet du SPAWN (rien lancé ici ; éditeur fermé ; lp-commandlets) :
    .\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=getSkeletalMeshFromPath -Pins -Exec'
    .\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=LoadHairMeshIfNeeded -Pins -Exec'
    .\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=LoadOtherMeshIfNeeded_0 -Pins -Exec'
    .\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=LoadDefaultTextures -Pins -Exec'
    .\Tools\RunCommandlet.ps1 -Lot STE-D9 -Commandlet LPDumpFunction -Args '-BP=GI -Fn=GeneratePerson -Pins -Exec'
    
  • Mesure pour nommer les paquets (même build, même parcours, sans -llm -llmcsv) : & "D:\Game\LifePlay_Build_5.8_STE-J5_Development\Windows\LifePlay.exe" -csvGpuStats '-ExecCmds=TrackAsyncLoadRequests.Enable 1, csvprofile frames=9000'. Le rapport Saved\Profiling\AsyncLoadRequests_*.log est écrit à la fin de la capture (AsyncPackageLoader.cpp:398-401, 599). Il ne sépare pas les demandes synchrones des asynchrones (estimé) ; son format exact est incertain.
  • Pour la fiche H8 (information, sans toucher à son livrable) : avec la correction d'une trame, les 8 trames longues GenerateRecs de CSV0 font 2 230 ms, dont 1 410 ms de TimerManager.

Questions à l'utilisateur

  • Q-H7-1 : les FAL ne font que 5 % des trames SPAWN. Faut-il les traiter (P1, P2 ou P3), ou s'abstenir et ouvrir H7b sur FlushLatentActions (64 à 77 %) ?
  • Q-H7-2 : lancer les 5 dumps STE-D9 du § 5 (nœud du FAL au SPAWN, contenu de getSkeletalMeshFromPath) ?
  • Q-H7-3 : refaire une mesure avec TrackAsyncLoadRequests.Enable 1, sans -llm -llmcsv, pour nommer les paquets ?
  • Q-H7-4 : si les FAL sont traités, quelle voie : P2 (vêtements asynchrones, local, gain invisible), P3 (cache, mémoire à plafonner) ou P1 (préchargement au lieu, gain ≈ 5 % au SPAWN, plus invasif) ?
  • Q-H7-5 : faut-il corriger la méthode de classement des trames (décalage d'une trame de mesure_h4.py et de FrameTime) avant d'arbitrer H8, et reclasser STE-H4-MESURE.md § 4 ?
  • Q-H7-6 : pour P3, quel budget mémoire pour les vêtements gardés (à mesurer par obj list class=SkeletalMesh et LLM Meshes) ?

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

  • A Docs/Migration/etat/ST/STE/STE-H7-DIAG.md

Tours : 46 / 60

Constats

  • Outils hors Grep, Read et Glob, tous en lecture seule : 7 passages Python lus sur l'entrée standard (python -I - <M> <CSV0> <CSV1> <<'EOF' … EOF), aucun fichier écrit. Ils ont servi à l'alignement, aux classes, aux coûts et aux appariements des vêtements. Le code n'est pas rangé : l'outil Tools/FrameStats.py déjà proposé par STE-H4 devrait intégrer le décalage de FrameTime (§ 3.2).
  • Grep sur un dossier sous Docs\Migration\etat saute les .txt, ignorés par git (0 résultat sur dumps\). Il faut passer le chemin du fichier. Grep sur des .uasset n'est pas probant : getSkeletalMeshFromPath → 0 dans Content\BPs, alors que le nœud existe (PEG4:10519).
  • getSkeletalMeshFromPath est une fonction de GI (appel GI_C::getSkeletalMeshFromPath, PEG4:10519). LMIN:145 l'affiche ?::.
  • Rien n'a été lancé (éditeur, commandlet, build, jeu) ; aucune écriture dans Source ni Content ; aucune commande git qui écrit.
  • Nombre de tours estimé ; python -I Tools/FicheStats.py --lot STE fait foi.