LifePlay-Revival/Docs/Migration/etat/ST/STE/STE-ORG5-I2a-IMPL.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

Fiche STE-ORG5-I2a — Niveau N3 — Modèle prévu (modèle de l'agent lp-impl) | utilisé claude-opus-5-5 — Agent lp-impl — Skills sk-protocol, ue-cpp-build, lp-build, lp-ue-compat (préchargés) ; sk-change-safety non invoqué (écart, § 4)

STE-ORG5-I2a-IMPL : affichage des récompenses ORG5 appliquées dans EquipMenu (2026-10-14)

Sources : fiche fiches\STE-ORG5-I2a.md ; SPEC STE-ORG5-I2-SPEC.md option (a) et « Décisions de l'utilisateur » (Q-I2-1 à Q-I2-4) ; code I1 (LPBonusRewardSubsystem.h 119 lignes, .cpp 668 lignes, lus en entier avant modification). Moteur lu : D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime (ENG ci-dessous). Ni compilation, ni éditeur, ni MCP, ni jeu.

1. Fichiers modifiés (chemins, lignes)

Contrôle préalable : UMG est dans les dépendances du module, Source\LifePlay\LifePlay.Build.cs:38 (PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore", "UMG" });) : pas d'arrêt.

D:\Game\LifePlay-master 5.8\Source\LifePlay\LPBonusRewardSubsystem.h (119 → 165 lignes) :

  • :34-41 commentaire d'en-tête I2a ; :52-55 déclarations avancées UPanelWidget, UTextBlock, UUserWidget, UVerticalBox ;
  • :86-90 struct FLPBonusRewardLine { TWeakObjectPtr<UTextBlock> Line; double ExpireAt; } (une échéance par ligne) ;
  • :132 FirstWarning ; :134-148 ShowAppliedReward, FindCursor, FindEquipMenu, EnsureEquipMenuHeight, EnsureRewardBox, ExpireRewardLines, ClearRewardDisplay ;
  • :158 RewardLines, :160 TWeakObjectPtr<UVerticalBox> RewardBox, :162 bInScene, :164 bDisplayTargetLogged.

D:\Game\LifePlay-master 5.8\Source\LifePlay\LPBonusRewardSubsystem.cpp (668 → 1028 lignes) :

  • :1 en-tête ; :7-13, :18 inclusions UMG / Slate (Blueprint/UserWidget.h, Blueprint/WidgetTree.h, Components/CanvasPanelSlot.h, PanelWidget.h, TextBlock.h, VerticalBox.h, VerticalBoxSlot.h, Styling/CoreStyle.h) ;
  • :30-39 constantes (RewardLineSeconds = 20.0, MaxRewardLines = 1, EquipMenuMinHeight = 500.0, RewardFontSize = 16, RewardOutlineSize = 2, noms Cursor, EquipMenu, ActionText, LPBonusRewardBox) ; :42-49 RemoveLine (retrait si IsValid) ; :55-62 RewardFont ;
  • :205-240 ReadStrings : D2, éléments FString, FName ou FText acceptés ;
  • :263-273 Deinitialize : ClearRewardDisplay(false), RewardBox.Reset() ; :413 Tick appelle ExpireRewardLines() après FirePending() ;
  • :417-446 Poll : bInScene ; ClearRewardDisplay(true) quand SexOn est faux ou absent ; D1 pour les trois avertissements du sondage (GI.SexOn, GI.SexPersons, Person.Sex_OrgasmCounts, :423, :447, :466) ;
  • :574-576 OnOrgasm : ShowAppliedReward(Name, Stats[0], Change) après le journal de la récompense (donc après ModifyStat réussi et après le retour bDisabled) ;
  • :579-641 ShowAppliedReward ; :643-668 FindCursor ; :670-686 FindEquipMenu ; :688-719 EnsureEquipMenuHeight ; :721-801 EnsureRewardBox ; :803-820 ExpireRewardLines ; :822-838 ClearRewardDisplay ; :1011-1028 FirstWarning, WarnOnce réécrit sur FirstWarning.

1.1 Comportement écrit (décisions de l'utilisateur)

Décision Où Comment
Emplacement (option a) EnsureRewardBox :721-801 UVerticalBox nommée LPBonusRewardBox ajoutée par UPanelWidget::AddChild (en dernier) à EquipMenu ; slot Fill si UVerticalBoxSlot (comme BonusRewardBox, SPEC A4)
Q-I2-1 lignes seules ShowAppliedReward :619-634 UTextBlock « Bonus : %s %s %+.1f » (prénom, stat, valeur, format du journal), blanc (1,1,1,1), police d'ActionText (sinon FCoreStyle « Regular ») en taille 16, contour (2, noir alpha 0,8), slot Padding(10, 5, 10, 0), HAlign_Left ; ni en-tête ni bouton
Q-I2-2 minuterie 20 s par ligne :636-639, ExpireRewardLines :803-820 échéance FPlatformTime::Seconds() + 20 par ligne ; relue à chaque passage du ticker existant (0,1 s) ; ligne retirée par RemoveFromParent ; remplacement à la récompense suivante (MaxRewardLines = 1, :612-617)
Q-I2-3 hauteur 500 si inférieure EnsureEquipMenuHeight :688-719 UCanvasPanelSlot::GetSize().Y inférieur à 500 → SetSize(FVector2D(X, 500)), si le slot n'est ni en taille automatique ni étiré verticalement ; une ligne de journal à chaque agrandissement
Q-I2-4 bDisabled coupe tout OnOrgasm (retour existant avant Pick) ShowAppliedReward n'est appelé qu'après ModifyStat réussi : bDisabled vrai → ni application, ni case, ni ligne ; aucune ligne ne survit à la scène (garde 3)

1.2 Gardes de Big Boss : où elles sont tenues

Garde Tenue Lignes
(1) Cursor ou EquipMenu introuvable : pas de plantage, avertissement unique, récompense gardée l'affichage vient après ModifyStat et le journal ; chaque échec rend sans effet, un avertissement par cause (FirstWarning) : GI.Cursor (propriété absente), GI.Cursor.Value (vide, pas un UUserWidget, ou sans WidgetTree), Cursor.EquipMenu (absent ou pas un UPanelWidget, classe trouvée journalisée), LPBonusRewardBox.Type, EquipMenu.AddChild ; hauteur : EquipMenu.Slot, EquipMenu.Slot.Layout (affichage maintenu) .cpp:574-576, :643-686, :762-783, :696-713
(2) aucun ajout en double si EquipMenu est reconstruit case retrouvée avant toute création, dans l'ordre : pointeur faible RewardBox encore enfant de cet EquipMenu ; enfant d'EquipMenu nommé LPBonusRewardBox ; objet de ce nom dans l'arbre de Cursor (FindObjectFast, case détachée : reprise, pas de NewObject du même nom) ; création seulement sinon. Pointeurs faibles seulement (RewardBox, lignes) .cpp:726-761, .h:86-90, :160
(3) minuteries nettoyées en fin de scène et dans Deinitialize Poll : SexOn faux (ou absent) → ClearRewardDisplay(true) (lignes retirées, échéances vidées) ; Deinitialize : ticker retiré, ClearRewardDisplay(false) (échéances vidées, widgets laissés au monde qui se ferme), RewardBox.Reset() ; tempo fini après la scène : pas de ligne (bInScene, :583-588) .cpp:263-273, :417-446, :822-838

1.3 Appels moteur et preuves (UE 5.8, ENG)

Appel Source
UUserWidget::GetWidgetFromName(const FName&) const = WidgetTree ? WidgetTree->FindWidget(Name) : nullptr ; WidgetTree public UMG\Public\Blueprint\UserWidget.h:1426, :1524 ; UMG\Private\UserWidget.cpp:1259-1262
UWidgetTree::FindWidget(const FName&) parcourt l'arbre (ForEachWidgetUntil) : un widget détaché n'est pas trouvé UMG\Public\Blueprint\WidgetTree.h:34 ; UMG\Private\WidgetTree.cpp:40-54
UWidgetTree::ConstructWidget<T>(TSubclassOf<T>, FName = NAME_None) [[nodiscard]] = NewObject<T>(this, Class, Name, RF_Transactional) pour un UWidget WidgetTree.h:105-122 (:117)
UPanelWidget::GetChildrenCount, GetChildAt, AddChild (rend UPanelSlot*), ClearChildren UMG\Public\Components\PanelWidget.h:28, :36, :59, :122
UWidget::Slot public (TObjectPtr<UPanelSlot>), GetParent(), RemoveFromParent() UMG\Public\Components\Widget.h:222 (public:), :264, :769, :776
UVerticalBox::AddChildToVerticalBox → UVerticalBoxSlot* ; UVerticalBoxSlot::SetPadding, SetSize, SetHorizontalAlignment VerticalBox.h:30 ; VerticalBoxSlot.h:53, :58, :63
FSlateChildSize(ESlateSizeRule::Type), ESlateSizeRule::Fill UMG\Public\Components\SlateWrapperTypes.h:155, :173, :118-119
UTextBlock::SetText(FText), SetColorAndOpacity(FSlateColor), GetFont() const (référence constante), SetFont(FSlateFontInfo) TextBlock.h:103, :114, :148, :156
FSlateFontInfo::OutlineSettings, Size (float) ; FFontOutlineSettings(int32, FLinearColor) ; OutlineSize int32, OutlineColor SlateCore\Public\Fonts\SlateFontInfo.h:160, :175, :78, :42, :67
FCoreStyle::GetDefaultFontStyle(const FName, const float, …) SlateCore\Public\Styling\CoreStyle.h:51
UCanvasPanelSlot::SetSize(FVector2D), GetSize(), GetAnchors(), GetAutoSize() ; FAnchors::IsStretchedVertical() CanvasPanelSlot.h:112, :116, :132, :148 ; Slate\Public\Widgets\Layout\Anchors.h:78
FindObjectFast<T>(UObject* Outer, FName Name, EFindObjectFlags = None, EObjectFlags = RF_NoFlags) CoreUObject\Public\UObject\UObjectGlobals.h:2059-2061
TPropertyTypeFundamentals::GetPropertyValue(void const*) (statique) ; FScriptArrayHelper(const FArrayProperty*, const void*) ; FNameProperty = TProperty_WithEqualityAndSerializer<FName, FProperty> ; FTextProperty = TProperty<FText, FProperty> ; FStrProperty = TProperty_WithEqualityAndSerializer<FString, FProperty> UnrealType.h:1488, :4285, :3723 ; TextProperty.h:20 ; StrProperty.h:10 + StrProperty.h.inl:27
Précédents du dépôt suivis : police copiée d'un voisin LPColorGradeSubsystem.cpp:295 ; TWeakObjectPtr<UTextBlock> sur déclaration avancée dans un en-tête UCLASS LPColorGradeSubsystem.h:20, :53 ; Slot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)) LPColorGradeSubsystem.cpp:217.

1.4 Choix de la minuterie (demandé par la fiche, rubrique 4)

FTSTicker existant (déjà ajouté dans Initialize, retiré dans Deinitialize) plutôt que FTimerManager du monde : (a) le sous-système appartient au GameInstance, qui survit aux changements de niveau, alors que les minuteries d'un monde disparaissent avec lui ; (b) aucune poignée de plus à garder : la « poignée » d'une ligne est son entrée dans RewardLines (pointeur faible + échéance), vidée en fin de scène et dans Deinitialize ; (c) même horloge (FPlatformTime::Seconds) que le tempo de 0,2 s d'I1. Conséquences (estimé) : délai en temps réel, insensible à une pause ou une dilatation du temps de jeu ; précision 0,1 s (retrait entre 20,0 et 20,1 s).

1.5 Mesures (commandes et sorties brutes)

Avant modification :

git -C "D:/Game/LifePlay-master 5.8" status --porcelain -- Source Content Config ; rev-parse --short HEAD ; md5sum …
 M Content/BPs/GI.uasset
 M Content/BPs/PC.uasset
 M Content/BPs/Person.uasset
 M Content/BPs/Room.uasset
 M Content/UI/TopRight.uasset
 M Source/LifePlay/LPDebugSubsystem.cpp
 M Source/LifePlay/LPDebugSubsystem.h
 M Source/LifePlay/LPDirectControlSubsystem.cpp
 M Source/LifePlay/LPDirectControlSubsystem.h
 M Source/LifePlay/LPOutdoorRoomSubsystem.cpp
 M Source/LifePlay/LPOutdoorRoomSubsystem.h
 M Source/LifePlay/LifePlay.cpp
?? Source/LifePlay/LPBonusRewardSubsystem.cpp
?? Source/LifePlay/LPBonusRewardSubsystem.h
?? Source/LifePlay/LPCameraModeSubsystem.cpp
?? Source/LifePlay/LPCameraModeSubsystem.h
?? Source/LifePlay/LPSaveRelaunch.cpp
?? Source/LifePlay/LPSaveRelaunch.h
0745917
aa56bd8a655ecc55818a4aabeae3c028 *D:/Game/LifePlay-master 5.8/Source/LifePlay/LPBonusRewardSubsystem.h
20abc05064db9fbce3440b5057c6af8a *D:/Game/LifePlay-master 5.8/Source/LifePlay/LPBonusRewardSubsystem.cpp

Après modification (for f in …; do printf …; cat "$f" | tr -cd '\r' | wc -c; …; cat "$f" | wc -l; done; md5sum …; git … status --porcelain -- Source Content Config) :

LPBonusRewardSubsystem.h CR=0
LPBonusRewardSubsystem.h lignes=165
LPBonusRewardSubsystem.cpp CR=0
LPBonusRewardSubsystem.cpp lignes=1028
87177c7a53fb5b5cc946e904060a3aaa *LPBonusRewardSubsystem.h
16147626d2fa8a11dac8747cdbe12e9a *LPBonusRewardSubsystem.cpp

suivi des 18 mêmes lignes de git status qu'avant, dans le même ordre (aucun autre fichier touché ; les deux fichiers restent ??). Grep d'un nom d'utilisateur et d'un chemin de profil sur LPBonusRewardSubsystem.* (motif non recopié ici : il contient le nom) : Found 0 total occurrences across 0 files. Grep AddToViewport|AddViewportWidgetContent|CreateWidget|UButton : aucune ligne dans le .cpp (le motif combiné n'a sorti que GetWidgetFromName, ConstructWidget, LPBonusRewardBox et les appels internes, .cpp:39, :57, :621, :674, :759).

2. Écarts à la SPEC

  • E1 (décision Q-I2-2, voulu) : SPEC 2.2 « Durée : … Aucune minuterie » remplacé par une échéance de 20 s par ligne, lue par le ticker existant (1.4), en plus du retrait en fin de scène et du remplacement.
  • E2 : SPEC 2.2 points 3 et 4 écrivent NewObject<UVerticalBox>(Equip, …) et NewObject<UTextBlock>(Box) ; écrit : Cursor->WidgetTree->ConstructWidget<…> (outer = arbre de widgets de Cursor, voie UMG de création à l'exécution, NewObject appelé dedans, WidgetTree.h:117). La boîte est créée dans EnsureRewardBox, appelée seulement par ShowAppliedReward : critère V5-4 à lire ainsi.
  • E3 (garde 2 de Big Boss) : recherche de la case élargie (enfant d'EquipMenu par nom, puis objet de ce nom dans l'arbre) au lieu du seul test « RewardBox invalide ou parent différent » ; une case retrouvée ou reprise est vidée (ClearChildren) et les échéances remises à zéro.
  • E4 : remplacement par retrait des lignes suivies (MaxRewardLines = 1, RemoveFromParent) au lieu de Box->ClearChildren() à chaque affichage ; même effet visible (une ligne au plus, V6-3) ; passer la constante à 3 donnerait l'empilement, chaque ligne gardant ses 20 s.
  • E5 : hauteur 500 seulement si le slot n'est ni en taille automatique ni étiré verticalement (sinon GetSize().Y n'est pas une hauteur) ; avertissement unique si le slot n'est pas un UCanvasPanelSlot ; ligne de journal hauteur d'EquipMenu portee de X a 500 à chaque agrandissement (non prévue par la SPEC).
  • E6 : pas de ligne si la scène s'est terminée pendant le tempo de 0,2 s (bInScene, journal Verbose) ; la SPEC ne traitait pas ce cas.
  • E7 : Deinitialize vide les échéances sans retirer les widgets (choix de sûreté en fermeture) ; la fin de scène, elle, retire les lignes.
  • E8 : avertissements uniques par cause (7 clés, 1.2), comme I1 et la SPEC R1, et non un seul avertissement global.
  • E9 : police de repli FCoreStyle::GetDefaultFontStyle("Regular", 16) si ActionText est absent (la SPEC ne prévoyait pas de repli) ; bApplyOutlineToDropShadows laissé par défaut.
  • E10 : Poll vide aussi l'affichage quand la propriété SexOn est absente.
  • E11 (hors SPEC, permis par la fiche rubrique 3) : D1 corrigé par FirstWarning pour le nouveau code et les trois avertissements de Poll (sondé toutes les 0,1 s) ; D2 corrigé (ReadStrings accepte FName et FText).

3. Instantané (lot, manifeste)

Aucun instantané : Tools\Snapshot.ps1 copie vers D:\Game\LifePlay_Backup_5.5\<Lot>\ (Tools\Snapshot.ps1:6-7, :22, :26) et Glob **/LPBonusRewardSubsystem.cpp sous D:\Game\LifePlay_Backup_5.5 → No files found ; idem sous Saved\ de la copie 5.8. Les deux fichiers sont non suivis (??) : la version I1 n'est récupérable ni par git ni par une sauvegarde. Empreintes de la version I1 : .h aa56bd8a655ecc55818a4aabeae3c028, .cpp 20abc05064db9fbce3440b5057c6af8a ; son texte complet n'existe plus que dans la transcription de cette session (lecture intégrale au premier tour). Le repli SPEC § 3 « git restore des deux fichiers » est donc impossible tant qu'I1 n'est pas commité ; le repli reste possible en retirant les appels ShowAppliedReward (.cpp:576), ClearRewardDisplay (:270, :429, :440) et ExpireRewardLines (:413).

4. Ce qui n'a pas été fait

  • Aucune compilation (build COMMUN-8, fiche BUILD de lp-builder), aucun essai en jeu, aucun éditeur.
  • sk-change-safety (« à invoquer », fiche rubrique 2b) n'a pas été invoqué et aucun instantané n'a été pris avant la modification (§ 3) : écart de méthode.
  • D1 non corrigé aux appels de WarnOnce liés à un orgasme (GI.CurrentTags, isPlayer, PlayerCharacter, IsInterestedIn, QueryStat, ModifyStat, Name_First) : message encore construit à chaque appel, mais ces appels sont rares (un par orgasme).
  • Pas d'en-tête vert, pas de bouton, pas de grille V-1 (Q-I2-1, SPEC § 5).

5. Points à confirmer en VAL

  • V5-2 : compilation 5.8 sans erreur ni avertissement nouveau sur les deux fichiers (code jamais compilé).
  • Compilation : TWeakObjectPtr<UTextBlock> dans FLPBonusRewardLine (.h:88) sur déclaration avancée, comme LPColorGradeSubsystem.h:53.
  • Compilation : FindObjectFast<UObject>(Tree, BoxName) à deux arguments (UObjectGlobals.h:2059).
  • Compilation : ConstructWidget<UTextBlock>(UTextBlock::StaticClass()) et <UVerticalBox>(…, BoxName) ([[nodiscard]], résultat utilisé).
  • Compilation : GetPropertyValue(const void*) sur FStrProperty, FNameProperty, FTextProperty (.cpp:227-235).
  • Compilation : Equip->Slot.Get(), Cursor->WidgetTree.Get(), FCoreStyle::GetDefaultFontStyle("Regular", float).
  • Compilation unitaire : fonctions RemoveLine et RewardFont dans l'espace de noms nommé LPBonusReward (pas de collision C2374 attendue).
  • V5-3 : 0 AddToViewport, AddViewportWidgetContent, CreateWidget, UButton dans le .cpp.
  • V5-4 (lu avec E2) : création des widgets seulement dans ShowAppliedReward / EnsureRewardBox, jamais dans Poll ni Initialize.
  • V5-5 : appel d'affichage après ModifyStat réussi et après le retour bDisabled (.cpp:574-576).
  • V5-6 (lu avec E3 à E5) : GI.Cursor → GetWidgetFromName("EquipMenu") → Cast<UPanelWidget> ; police 16, contour (2, noir 0,8), marge (10, 5, 10, 0) ; 500 seulement si inférieur.
  • V6-2 : journal Bonus : affichage dans EquipMenu (classe VerticalBox, 4 enfants) une fois par session (4 = les 3 enfants du dump + la case, compte pris après l'ajout).
  • V6-2 : journal Bonus : hauteur d'EquipMenu portee de 350 a 500 au premier affichage (puis à chaque Cursor recréé).
  • V6-1 : ligne visible menu ouvert, sous la grille d'objets, alignée à gauche (+ 10), blanche à contour noir.
  • V6-8 (nouveau, Q-I2-2) : la ligne disparaît seule entre 20,0 et 20,1 s après son apparition, menu ouvert ou fermé puis rouvert.
  • V6-3 : second orgasme avant 20 s : ligne remplacée, nouvelle échéance de 20 s.
  • V6-4 : fin de scène puis nouvelle scène : aucune ligne.
  • V6-5 : bDisabled=True : aucune case créée, aucune ligne, aucun journal d'affichage.
  • V6-7 : 0 plantage, 0 Accessed None, aucun avertissement Bonus : de widget nouveau par rapport à COMMUN-6.
  • Mise en page : ScrollBox_3 (Fill) partage désormais la place restante avec la case (Fill), même vide : vérifier que la liste de bondage reste lisible à 500 de haut.
  • Pause éventuelle du jeu : le délai de 20 s court en temps réel (1.4).

6. Fichiers à commiter

Dépôt du jeu D:\Game\LifePlay-master 5.8, branche ue5.8 ; les deux sources sont non suivies : I1 et I2a partent ensemble (SPEC R7).

  • A Source/LifePlay/LPBonusRewardSubsystem.h
  • A Source/LifePlay/LPBonusRewardSubsystem.cpp
  • A Docs/Migration/etat/ST/STE/STE-ORG5-I2a-IMPL.md

Tours : 24 / 40 (comptés à la main, remise du compte rendu comprise ; python -I Tools/FicheStats.py --lot STE fait foi)

Constats

  • F-I2a-1 : les fichiers I1 sont non suivis et sans instantané ; toute nouvelle fiche qui les modifie avant le commit du gardien efface la version précédente sans retour possible. Recommandation : commit I1 + I2a avant toute autre fiche sur ces fichiers.
  • F-I2a-2 : FStrProperty n'a pas de ligne class FStrProperty : la classe est produite par macro (StrProperty.h:10, StrProperty.h.inl:27) ; une recherche de la déclaration doit passer par StrProperty.h.inl.
  • F-I2a-4 : la première version de ce livrable recopiait le motif du contrôle « aucun nom d'utilisateur » ; détecté par le même Grep (1 occurrence) et corrigé (même piège que SPEC A.12).
  • F-I2a-3 : NewObject d'un nom déjà pris dans le même outer remplace l'objet existant (estimé, non relu dans StaticAllocateObject) : d'où la recherche par FindObjectFast avant ConstructWidget avec un nom fixe.