L7i : mesure 4.27/5.5 (Dress preexistant) et capteur cible pour les variables DSL Person vides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
edujiro5972 2026-09-30 20:38:09 +02:00
parent 996215ef9b
commit 0376fb7861
4 changed files with 108 additions and 22 deletions

View file

@ -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 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. 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`).

View file

@ -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-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-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-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-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-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 | | 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 |

View file

@ -135,6 +135,49 @@ void ULPSessionLogSubsystem::Initialize(FSubsystemCollectionBase& Collection)
StartScriptExceptionTrace(); 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<FStructProperty>(Prop)) { TypeName = SP->Struct->GetName(); }
if (const FArrayProperty* AP = CastField<FArrayProperty>(Prop))
{
if (const FStructProperty* ISP = CastField<FStructProperty>(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<FString>& Keywords)
{
if (!Frame.Node || !Frame.Locals)
{
return;
}
int32 Shown = 0;
for (TFieldIterator<FProperty> 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<void>(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() void ULPSessionLogSubsystem::StartScriptExceptionTrace()
{ {
FString Filter; FString Filter;
@ -149,11 +192,25 @@ void ULPSessionLogSubsystem::StartScriptExceptionTrace()
int32 Max = 3; int32 Max = 3;
FParse::Value(FCommandLine::Get(), TEXT("LPTraceMax="), Max); 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<FString> 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<FString> GIVars;
GIVarsList.ParseIntoArray(GIVars, TEXT(","), true);
const TWeakObjectPtr<UGameInstance> 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<int32> Count = MakeShared<int32>(0); TSharedRef<int32> Count = MakeShared<int32>(0);
ScriptExceptionHandle = FBlueprintCoreDelegates::OnScriptException.AddLambda( 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 : // 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. // on la reecrit pour toutes les exceptions, pas seulement celles relevees.
@ -165,35 +222,36 @@ void ULPSessionLogSubsystem::StartScriptExceptionTrace()
{ {
return; return;
} }
if (Functions.Num() > 0 && !Functions.Contains(StackFrame.Node->GetName()))
{
return;
}
++*Count; ++*Count;
UE_LOG(LogLifePlay, Warning, TEXT("[trace %d/%d] %s | objet %s | fonction %s | offset %d"), UE_LOG(LogLifePlay, Warning, TEXT("[trace %d/%d] %s | objet %s | fonction %s | offset %d"),
*Count, Max, *Description, *GetNameSafe(ActiveObject), *StackFrame.Node->GetName(), *Count, Max, *Description, *GetNameSafe(ActiveObject), *StackFrame.Node->GetName(),
StackFrame.Code && StackFrame.Node->Script.Num() ? (int32)(StackFrame.Code - StackFrame.Node->Script.GetData()) : -1); StackFrame.Code && StackFrame.Node->Script.Num() ? (int32)(StackFrame.Code - StackFrame.Node->Script.GetData()) : -1);
int32 Shown = 0; TraceFrameLocals(*Count, 0, StackFrame, Keywords);
for (TFieldIterator<FProperty> It(StackFrame.Node); It && Shown < 200; ++It) const FFrame* Caller = StackFrame.PreviousFrame;
for (int32 Level = 1; Level <= Depth && Caller; ++Level, Caller = Caller->PreviousFrame)
{ {
const FProperty* Prop = *It; TraceFrameLocals(*Count, Level, *Caller, Keywords);
FString TypeName = Prop->GetCPPType();
if (const FStructProperty* SP = CastField<FStructProperty>(Prop)) { TypeName = SP->Struct->GetName(); }
if (const FArrayProperty* AP = CastField<FArrayProperty>(Prop))
{
if (const FStructProperty* ISP = CastField<FStructProperty>(AP->Inner)) { TypeName = TEXT("TArray<") + ISP->Struct->GetName() + TEXT(">"); }
} }
const FString PropName = Prop->GetName(); if (const UGameInstance* GI = WeakGI.Get())
const bool bMatch = Keywords.ContainsByPredicate([&](const FString& K)
{ return PropName.Contains(K) || TypeName.Contains(K); });
if (!bMatch)
{ {
continue; for (const FString& Name : GIVars)
{
const FProperty* Prop = GI->GetClass()->FindPropertyByName(FName(*Name));
FString Value = TEXT("<introuvable>");
if (Prop)
{
Value.Reset();
Prop->ExportTextItem_Direct(Value, Prop->ContainerPtrToValuePtr<void>(GI), nullptr, const_cast<UGameInstance*>(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<void>(StackFrame.Locals), nullptr, nullptr, PPF_None);
UE_LOG(LogLifePlay, Warning, TEXT("[trace %d] %s (%s) = %s"), *Count, *PropName, *TypeName, *Value.Left(500));
++Shown;
} }
}); });
} }

View file

@ -43,6 +43,9 @@ private:
* exception de script Blueprint dont la description contient <texte> (3 premieres * exception de script Blueprint dont la description contient <texte> (3 premieres
* seulement, -LPTraceMax=N pour changer), ecrit la pile d'appel et les variables locales * 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. * du cadre dont le nom ou le type de structure contient l'un des mots.
* Lot L7i : -LPTraceFunctions=<f1,f2> ne retient que les exceptions levees dans ces fonctions ;
* -LPTraceDepth=N ecrit aussi les variables locales des N cadres appelants ;
* -LPTraceGIVars=<v1,v2> 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. * Sans l'option, rien n'est branche : le moteur garde son comportement de journal normal.
*/ */
void StartScriptExceptionTrace(); void StartScriptExceptionTrace();