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

View file

@ -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<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()
{
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<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);
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<FProperty> 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<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(">"); }
}
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("<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
* 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=<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.
*/
void StartScriptExceptionTrace();