From 0376fb7861f4470b020848e64adb5989d4bbb45c Mon Sep 17 00:00:00 2001 From: edujiro5972 <336072401+edujiro5972@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:38:09 +0200 Subject: [PATCH] L7i : mesure 4.27/5.5 (Dress preexistant) et capteur cible pour les variables DSL Person vides Co-Authored-By: Claude Opus 5.5 --- Docs/Migration/Journal_Modifications.md | 25 ++++++ Docs/Migration/Registre_Erreurs.md | 2 +- Source/LifePlay/LPSessionLogSubsystem.cpp | 100 +++++++++++++++++----- Source/LifePlay/LPSessionLogSubsystem.h | 3 + 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/Docs/Migration/Journal_Modifications.md b/Docs/Migration/Journal_Modifications.md index 992b33b..c28ed07 100644 --- a/Docs/Migration/Journal_Modifications.md +++ b/Docs/Migration/Journal_Modifications.md @@ -585,3 +585,28 @@ avec le build L7a (identique au contenu actuel), et vérifier le comportement en L7b validé en jeu : le build testé `LifePlay_Build_5.5_L7b` contient exactement ces 3 paquets. Leçon : avant de retirer un lot sur un symptôme visuel, vérifier la vitesse de jeu et demander si le défaut existe en 4.27 et dans le build précédent. + +### L7i — variables DSL `Person` vides (ERR-003 / ERR-030), 2026-09-30 + +Mesure (journaux `L7a_partie`, `L7b_partie`, 4.27 `LifePlay_Build_427_Dev\...\LifePlay.log`, par minute) : + +| Signature | 4.27 (23 min) | L7a (62 min) | L7b (45 min) | +|---|---|---|---| +| `Accessed None … Person` dans `GI::Dress` | 376 | 326 | 641 | +| `index 0 … array 'Person'` (variable DSL vide) | 9 | 179 | 56 | +| `index … array 'Floats'` | 2,4 | 31 | 64 | +| `Accessed None … Person` dans `getActorVar` | 0 | 45 | 7,6 | +| `Accessed None … Person` dans `ReturnStatIfNeeded` | 0,35 | 33 | 5,6 | + +Conclusion : **ERR-030 (`Dress`) existe en 4.27** (même signature `Dress:0E35`, même ordre de grandeur) : préexistant, +hors périmètre. L'écart propre à 5.5 porte sur les variables DSL vides (ERR-003). Lecture du graphe +(`GI::GetVariable`) : la variable `Player` est construite avec `Person = [Special]`, `Special = GI.PlayerCharacter` +(`SwitchString_55`, branche `Player`) ; le capteur L6a montre `Special = None`. Les erreurs surviennent tout au long de +la partie, pas seulement au chargement. + +| CHG | Date | Lot | Fichiers | Raison | Retour arrière | +|---|---|---|---|---|---| +| CHG-040 | 2026-09-30 | L7i | `Source/LifePlay/LPSessionLogSubsystem.h/.cpp` : options `-LPTraceFunctions`, `-LPTraceDepth` (variables locales des cadres appelants), `-LPTraceGIVars` (variables de l'instance de jeu) ; inactives sans `-LPTraceScriptEx` | capteur ciblé ERR-003 : quelle scène et quelle variable restent vides | `git checkout L7i-avant -- Source` | + +Build de diagnostic `LifePlay_Build_5.5_L7i` (contenu identique à L7b, 8561 `.uasset`), lancement par +`LifePlay_diagnostic_L7i.bat` (300 relevés, 4 cadres appelants, `-ScriptStackOnWarnings`). diff --git a/Docs/Migration/Registre_Erreurs.md b/Docs/Migration/Registre_Erreurs.md index c221f3d..cab8739 100644 --- a/Docs/Migration/Registre_Erreurs.md +++ b/Docs/Migration/Registre_Erreurs.md @@ -34,7 +34,7 @@ | ERR-025 | `LPFixEditableTextBoxFont` : un `Make EditableTextBoxStyle` peut perdre sa police sans rebranchement selon l'ordre de parcours (`CharGen::Make_64`) | relecture du diff L4-CX-B02 | 1 nœud | haute (silencieuse) | L4-CX-B02 | corrigé dans la commandlet (CHG-020) ; présent probablement dans le contenu du 2026-09-07 | | ERR-026 | Build Development incomplète : 3 257 packages cuits au lieu de 8 561 ; la plupart des maillages, vêtements, coiffures et textures manquent (chargés par chemin à l'exécution, invisibles pour le cuiseur) | retour utilisateur sur `LifePlay_Build_5.5_L4` | 5 304 packages | **bloquante** | L4 | corrigé (CHG-025) : `+DirectoriesToAlwaysCook=(Path="/Game")` ; test T-PKG-01 ajouté | | ERR-029 | Cuisson L5d : 95 avertissements (L5c : 0) | `L5d_BuildCookRun.log` | 95 | faible | L7a | **accepté** : ≈ 90 dépréciations `Get SkeletalMesh` (R-03) + 3 `GetRandomPointInNavigableRadius` (R-04) + 1 Unreal Trace Server ; visibles car la cuisson itérative a recompilé les 17 Blueprints modifiés par CX-B08 (L5c n'en recompilait aucun). Pas de régression | -| ERR-030 | `Accessed None … Person` dans `GI::Dress` (appelée par `Room`) : habillage des PNJ de pièce | partie V1 du 2026-09-30 (42 min, 271 personnages) | 20 189 (74,5/perso ; 4.27 : 0) | faible (situationnel, pas d'effet visible) | L6b | ouvert — 0 occurrence sur la partie L6a (65 personnages) : dépend du lieu ou de la scène. Lié à ERR-003 : variables de scène `Person` vides 15 à 20/perso en 5.5 contre 3,3 en 4.27 ; `ReturnStatIfNeeded` lit la variable `Player` au tableau `Person` vide (capteur L6a). Le capteur doit pouvoir filtrer par fonction ; partie V1rc (2 h, 339 persos) : `Dress` 283,6/perso en rafales sur 25 minutes (jusqu'à 23 096/min), autres fonctions stables par personnage (`getActorVar` 4,0, `ReturnStatIfNeeded` 3,8) → situationnel, sans effet visible signalé ; lot L7i | +| ERR-030 | `Accessed None … Person` dans `GI::Dress` (appelée par `Room`) : habillage des PNJ de pièce | partie V1 du 2026-09-30 (42 min, 271 personnages) | 20 189 (74,5/perso ; 4.27 : 0) | faible (situationnel, pas d'effet visible) | L7i | **préexistant en 4.27** (L7i : même signature `Dress:0E35`, 376/min en 4.27 contre 326 à 641 en 5.5) : hors périmètre. Historique — 0 occurrence sur la partie L6a (65 personnages) : dépend du lieu ou de la scène. Lié à ERR-003 : variables de scène `Person` vides 15 à 20/perso en 5.5 contre 3,3 en 4.27 ; `ReturnStatIfNeeded` lit la variable `Player` au tableau `Person` vide (capteur L6a). Le capteur doit pouvoir filtrer par fonction ; partie V1rc (2 h, 339 persos) : `Dress` 283,6/perso en rafales sur 25 minutes (jusqu'à 23 096/min), autres fonctions stables par personnage (`getActorVar` 4,0, `ReturnStatIfNeeded` 3,8) → situationnel, sans effet visible signalé ; lot L7i | | ERR-031 | Le contact n'apparaît pas dans l'encadré lors d'un appel téléphonique | retour utilisateur 2026-09-30 | — | moyenne (visuel) | L6c | **préexistant en 4.27** (constaté par l'utilisateur le 2026-09-30, même encadré vide) : hors périmètre de la migration, non corrigé. Analyse : `UI/Scene::FaceRender` affiche `Render/SecondScreenMat` ← caméra `SecondScreen` de `CentralLondon` (liste « ShowOnly » : `Wall` + `PlayerCharacter`, scène à -3,1e6 / -1,5e7) ; dans la carte 4.27 d'origine, cette caméra a `RootComponent = None` et une position nulle, elle ne vise donc que le ciel | | ERR-032 | Scintillement à l'approche d'un personnage | retour utilisateur 2026-09-30 | — | moyenne (visuel) | L6a | **corrigé (CHG-031)**, validé en jeu le 2026-09-30 : on peut s'approcher des PNJ sans scintillement | | ERR-033 | Certaines actions demandent deux clics pour démarrer | retour utilisateur 2026-09-30 | — | moyenne (ergonomie) | L6d | **non reproduit** depuis le build L6e (constat utilisateur 2026-09-30) ; aucune modification ciblée — coïncide avec CX-B09 (marges des boutons), sans lien démontré ; sous surveillance, à rouvrir s'il réapparaît | diff --git a/Source/LifePlay/LPSessionLogSubsystem.cpp b/Source/LifePlay/LPSessionLogSubsystem.cpp index bfae0a1..8ff1670 100644 --- a/Source/LifePlay/LPSessionLogSubsystem.cpp +++ b/Source/LifePlay/LPSessionLogSubsystem.cpp @@ -135,6 +135,49 @@ void ULPSessionLogSubsystem::Initialize(FSubsystemCollectionBase& Collection) StartScriptExceptionTrace(); } +namespace +{ + /** Type lisible d'une propriete (nom de structure pour les structures et tableaux de structures). */ + FString TraceTypeName(const FProperty* Prop) + { + FString TypeName = Prop->GetCPPType(); + if (const FStructProperty* SP = CastField(Prop)) { TypeName = SP->Struct->GetName(); } + if (const FArrayProperty* AP = CastField(Prop)) + { + if (const FStructProperty* ISP = CastField(AP->Inner)) { TypeName = TEXT("TArray<") + ISP->Struct->GetName() + TEXT(">"); } + } + return TypeName; + } + + /** Ecrit les variables locales d'un cadre dont le nom ou le type contient l'un des mots. */ + void TraceFrameLocals(int32 Index, int32 Level, const FFrame& Frame, const TArray& Keywords) + { + if (!Frame.Node || !Frame.Locals) + { + return; + } + int32 Shown = 0; + for (TFieldIterator It(Frame.Node); It && Shown < 200; ++It) + { + const FProperty* Prop = *It; + const FString TypeName = TraceTypeName(Prop); + const FString PropName = Prop->GetName(); + const bool bMatch = Keywords.ContainsByPredicate([&](const FString& K) + { return PropName.Contains(K) || TypeName.Contains(K); }); + if (!bMatch) + { + continue; + } + FString Value; + Prop->ExportTextItem_Direct(Value, Prop->ContainerPtrToValuePtr(Frame.Locals), nullptr, nullptr, PPF_None); + UE_LOG(LogLifePlay, Warning, TEXT("[trace %d] %s%s (%s) = %s"), Index, + Level == 0 ? TEXT(" ") : *FString::Printf(TEXT(" [appelant %d %s] "), Level, *Frame.Node->GetName()), + *PropName, *TypeName, *Value.Left(500)); + ++Shown; + } + } +} + void ULPSessionLogSubsystem::StartScriptExceptionTrace() { FString Filter; @@ -149,11 +192,25 @@ void ULPSessionLogSubsystem::StartScriptExceptionTrace() int32 Max = 3; FParse::Value(FCommandLine::Get(), TEXT("LPTraceMax="), Max); - UE_LOG(LogLifePlay, Display, TEXT("Trace des exceptions de script : filtre '%s', variables [%s], %d releve(s) au plus"), *Filter, *LocalsList, Max); + // Lot L7i : filtre sur la fonction fautive, cadres appelants et variables de l'instance de jeu. + FString FunctionsList; + FParse::Value(FCommandLine::Get(), TEXT("LPTraceFunctions="), FunctionsList, false); + TArray Functions; + FunctionsList.ParseIntoArray(Functions, TEXT(","), true); + int32 Depth = 0; + FParse::Value(FCommandLine::Get(), TEXT("LPTraceDepth="), Depth); + FString GIVarsList; + FParse::Value(FCommandLine::Get(), TEXT("LPTraceGIVars="), GIVarsList, false); + TArray GIVars; + GIVarsList.ParseIntoArray(GIVars, TEXT(","), true); + const TWeakObjectPtr WeakGI = GetGameInstance(); + + UE_LOG(LogLifePlay, Display, TEXT("Trace des exceptions de script : filtre '%s', fonctions [%s], variables [%s], %d cadre(s) appelant(s), variables GI [%s], %d releve(s) au plus"), + *Filter, *FunctionsList, *LocalsList, Depth, *GIVarsList, Max); TSharedRef Count = MakeShared(0); ScriptExceptionHandle = FBlueprintCoreDelegates::OnScriptException.AddLambda( - [Filter, Keywords, Max, Count](const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info) + [Filter, Keywords, Max, Count, Functions, Depth, GIVars, WeakGI](const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info) { // Brancher un ecouteur supprime la pile d'appel que le moteur ecrit d'ordinaire : // on la reecrit pour toutes les exceptions, pas seulement celles relevees. @@ -165,35 +222,36 @@ void ULPSessionLogSubsystem::StartScriptExceptionTrace() { return; } + if (Functions.Num() > 0 && !Functions.Contains(StackFrame.Node->GetName())) + { + return; + } ++*Count; UE_LOG(LogLifePlay, Warning, TEXT("[trace %d/%d] %s | objet %s | fonction %s | offset %d"), *Count, Max, *Description, *GetNameSafe(ActiveObject), *StackFrame.Node->GetName(), StackFrame.Code && StackFrame.Node->Script.Num() ? (int32)(StackFrame.Code - StackFrame.Node->Script.GetData()) : -1); - int32 Shown = 0; - for (TFieldIterator It(StackFrame.Node); It && Shown < 200; ++It) + TraceFrameLocals(*Count, 0, StackFrame, Keywords); + const FFrame* Caller = StackFrame.PreviousFrame; + for (int32 Level = 1; Level <= Depth && Caller; ++Level, Caller = Caller->PreviousFrame) { - const FProperty* Prop = *It; - FString TypeName = Prop->GetCPPType(); - if (const FStructProperty* SP = CastField(Prop)) { TypeName = SP->Struct->GetName(); } - if (const FArrayProperty* AP = CastField(Prop)) - { - if (const FStructProperty* ISP = CastField(AP->Inner)) { TypeName = TEXT("TArray<") + ISP->Struct->GetName() + TEXT(">"); } - } + TraceFrameLocals(*Count, Level, *Caller, Keywords); + } - const FString PropName = Prop->GetName(); - const bool bMatch = Keywords.ContainsByPredicate([&](const FString& K) - { return PropName.Contains(K) || TypeName.Contains(K); }); - if (!bMatch) + if (const UGameInstance* GI = WeakGI.Get()) + { + for (const FString& Name : GIVars) { - continue; + const FProperty* Prop = GI->GetClass()->FindPropertyByName(FName(*Name)); + FString Value = TEXT(""); + if (Prop) + { + Value.Reset(); + Prop->ExportTextItem_Direct(Value, Prop->ContainerPtrToValuePtr(GI), nullptr, const_cast(GI), PPF_None); + } + UE_LOG(LogLifePlay, Warning, TEXT("[trace %d] GI.%s = %s"), *Count, *Name, *Value.Left(300)); } - - FString Value; - Prop->ExportTextItem_Direct(Value, Prop->ContainerPtrToValuePtr(StackFrame.Locals), nullptr, nullptr, PPF_None); - UE_LOG(LogLifePlay, Warning, TEXT("[trace %d] %s (%s) = %s"), *Count, *PropName, *TypeName, *Value.Left(500)); - ++Shown; } }); } diff --git a/Source/LifePlay/LPSessionLogSubsystem.h b/Source/LifePlay/LPSessionLogSubsystem.h index 8c6e2d4..7b7796e 100644 --- a/Source/LifePlay/LPSessionLogSubsystem.h +++ b/Source/LifePlay/LPSessionLogSubsystem.h @@ -43,6 +43,9 @@ private: * exception de script Blueprint dont la description contient (3 premieres * seulement, -LPTraceMax=N pour changer), ecrit la pile d'appel et les variables locales * du cadre dont le nom ou le type de structure contient l'un des mots. + * Lot L7i : -LPTraceFunctions= ne retient que les exceptions levees dans ces fonctions ; + * -LPTraceDepth=N ecrit aussi les variables locales des N cadres appelants ; + * -LPTraceGIVars= ecrit ces variables de l'instance de jeu a chaque releve. * Sans l'option, rien n'est branche : le moteur garde son comportement de journal normal. */ void StartScriptExceptionTrace();