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-41commentaire d'en-tête I2a ;:52-55déclarations avancéesUPanelWidget,UTextBlock,UUserWidget,UVerticalBox;:86-90struct FLPBonusRewardLine { TWeakObjectPtr<UTextBlock> Line; double ExpireAt; }(une échéance par ligne) ;:132FirstWarning;:134-148ShowAppliedReward,FindCursor,FindEquipMenu,EnsureEquipMenuHeight,EnsureRewardBox,ExpireRewardLines,ClearRewardDisplay;:158RewardLines,:160TWeakObjectPtr<UVerticalBox> RewardBox,:162bInScene,:164bDisplayTargetLogged.
D:\Game\LifePlay-master 5.8\Source\LifePlay\LPBonusRewardSubsystem.cpp (668 → 1028 lignes) :
:1en-tête ;:7-13,:18inclusions UMG / Slate (Blueprint/UserWidget.h,Blueprint/WidgetTree.h,Components/CanvasPanelSlot.h,PanelWidget.h,TextBlock.h,VerticalBox.h,VerticalBoxSlot.h,Styling/CoreStyle.h) ;:30-39constantes (RewardLineSeconds = 20.0,MaxRewardLines = 1,EquipMenuMinHeight = 500.0,RewardFontSize = 16,RewardOutlineSize = 2, nomsCursor,EquipMenu,ActionText,LPBonusRewardBox) ;:42-49RemoveLine(retrait siIsValid) ;:55-62RewardFont;:205-240ReadStrings: D2, élémentsFString,FNameouFTextacceptés ;:263-273Deinitialize:ClearRewardDisplay(false),RewardBox.Reset();:413TickappelleExpireRewardLines()aprèsFirePending();:417-446Poll:bInScene;ClearRewardDisplay(true)quandSexOnest faux ou absent ; D1 pour les trois avertissements du sondage (GI.SexOn,GI.SexPersons,Person.Sex_OrgasmCounts,:423,:447,:466) ;:574-576OnOrgasm:ShowAppliedReward(Name, Stats[0], Change)après le journal de la récompense (donc aprèsModifyStatréussi et après le retourbDisabled) ;:579-641ShowAppliedReward;:643-668FindCursor;:670-686FindEquipMenu;:688-719EnsureEquipMenuHeight;:721-801EnsureRewardBox;:803-820ExpireRewardLines;:822-838ClearRewardDisplay;:1011-1028FirstWarning,WarnOnceréécrit surFirstWarning.
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, …)etNewObject<UTextBlock>(Box); écrit :Cursor->WidgetTree->ConstructWidget<…>(outer = arbre de widgets deCursor, voie UMG de création à l'exécution,NewObjectappelé dedans,WidgetTree.h:117). La boîte est créée dansEnsureRewardBox, appelée seulement parShowAppliedReward: critère V5-4 à lire ainsi. - E3 (garde 2 de Big Boss) : recherche de la case élargie (enfant d'
EquipMenupar nom, puis objet de ce nom dans l'arbre) au lieu du seul test «RewardBoxinvalide 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 deBox->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().Yn'est pas une hauteur) ; avertissement unique si le slot n'est pas unUCanvasPanelSlot; ligne de journalhauteur 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, journalVerbose) ; la SPEC ne traitait pas ce cas. - E7 :
Deinitializevide 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)siActionTextest absent (la SPEC ne prévoyait pas de repli) ;bApplyOutlineToDropShadowslaissé par défaut. - E10 :
Pollvide aussi l'affichage quand la propriétéSexOnest absente. - E11 (hors SPEC, permis par la fiche rubrique 3) : D1 corrigé par
FirstWarningpour le nouveau code et les trois avertissements dePoll(sondé toutes les 0,1 s) ; D2 corrigé (ReadStringsaccepteFNameetFText).
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
WarnOncelié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>dansFLPBonusRewardLine(.h:88) sur déclaration avancée, commeLPColorGradeSubsystem.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*)surFStrProperty,FNameProperty,FTextProperty(.cpp:227-235). - Compilation :
Equip->Slot.Get(),Cursor->WidgetTree.Get(),FCoreStyle::GetDefaultFontStyle("Regular", float). - Compilation unitaire : fonctions
RemoveLineetRewardFontdans l'espace de noms nomméLPBonusReward(pas de collision C2374 attendue). - V5-3 : 0
AddToViewport,AddViewportWidgetContent,CreateWidget,UButtondans le.cpp. - V5-4 (lu avec E2) : création des widgets seulement dans
ShowAppliedReward/EnsureRewardBox, jamais dansPollniInitialize. - V5-5 : appel d'affichage après
ModifyStatréussi et après le retourbDisabled(.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 500au premier affichage (puis à chaqueCursorrecréé). - 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 avertissementBonus :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.hA Source/LifePlay/LPBonusRewardSubsystem.cppA 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 :
FStrPropertyn'a pas de ligneclass FStrProperty: la classe est produite par macro (StrProperty.h:10,StrProperty.h.inl:27) ; une recherche de la déclaration doit passer parStrProperty.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 :
NewObjectd'un nom déjà pris dans le même outer remplace l'objet existant (estimé, non relu dansStaticAllocateObject) : d'où la recherche parFindObjectFastavantConstructWidgetavec un nom fixe.