LifePlay-Revival/Docs/Migration/etat/CAM/CAM-B2p-I-VAL.md

210 lines
43 KiB
Markdown
Raw Normal View History

Fiche CAM-B2p-I-VAL — Niveau N1 — Modèle prévu sonnet | utilisé claude-sonnet-5-5 — Agent lp-validator — Skills sk-protocol, sk-recount, debug-verification, lp-validate
# CAM-B2p-I-VAL : validation statique de N2 (reprise de B2p-H) et de l'aide des contrôles derrière l'icône « i » (2026-10-14)
Portée : VAL **statique** (aucune compilation, aucun jeu, aucun éditeur, aucun commandlet, aucun MCP, aucune écriture git), par un validateur distinct de l'auteur.
Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8` (`git branch --show-current` : `ue5.8`), UE 5.8, BuildSettingsVersion V7 (`/we4456 /we4458 /we4459`).
`LPCameraModeSubsystem.cpp` et `.h` sont **non suivis** par git (`git status --short -- Source` : `??`) : pas de tag `CAM-B2p-I-avant`. Chaque point est jugé sur le diff entre instantanés (`git diff --no-index`), comme `CAM-B2p-H-VAL.md`. Convention : `CAM-B2p-I0` = état d'avant le point I0 ; I0 = diff `I0` vers `I1` ; I1 = diff `I1` vers final.
Niveaux de preuve : **établi** = lecture ou commande rejouée aujourd'hui ; **estimé** = déduit (raisonnement dit) ; **incertain** = non vérifié (essai nommé).
Commandes : Read, Grep, Glob ; deux scripts `.ps1` du dossier temporaire de session (`scratchpad\v1.ps1`, `scratchpad\v2.ps1`), lancés par `pwsh -NoProfile -File`, hors dépôt. `v1.ps1` a échoué (chemins des instantanés sans le sous-dossier `Source\LifePlay` : « ABSENT », diffs vides, rien d'utilisé) ; `v2.ps1` corrige les chemins et fait foi.
## 1. Fiches amont relues
- `Docs/Migration/etat/CAM/CAM-B2p-I-IMPL.md` (lue en entier ; seule IMPL du lot).
- `Docs/Migration/etat/CAM/CAM-B2p-I-SPEC.md` (lue en entier : DIAG § 1, SPEC § 2, risques § 3, essais § 4).
- `Docs/Migration/etat/CAM/CAM-B2p-H-VAL.md` (lue en entier : forme d'une VAL statique C++ de ce lot ; son N2 à la l. 188).
- Lus en lecture seule : `CAM-B2p-G-PC-VAL_LPDumpFunction_20261014-135527.txt` (l. 345-352, 1006-1014, 1163-1172, 3005-3043), `CAM-B2p-E-IMPL.md` l. 44-49, `CAM-B2p-G-SPEC.md` l. 11-14, `Config/DefaultInput.ini` l. 97, 118, 150-160, `LPBonusRewardSubsystem.cpp` l. 590-713, `LPKeyboardSubsystem.cpp` l. 155-180 et 599, `LifePlay.Build.cs` l. 12-52, `Intermediate/Build/Win64/x64/*/*/LifePlay/Module.LifePlay.cpp` (trois cibles).
- Gabarit VAL lu : `D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md` (section VAL, sections 1 à 6 dans l'ordre).
- **Gabarit de l'IMPL (écart, N5)** : l'IMPL n'a ni tête commune (« Fiche … — Niveau … »), ni section « Ce qui n'a pas été fait », ni ligne `Tours :`. Ses sections sont numérotées 0 à 5 et non 1 à 6 ; les « points à confirmer » sont en § 4 (essais CAM-86 à CAM-90), les écarts en § 3, les fichiers à commiter en § 5. Le contenu est exact sauf un chiffre (voir § 3, contrôle 3).
## 2. Points à confirmer des IMPL (un par ligne)
| # | Point | Verdict | Preuve |
|---|---|---|---|
| I0 | N2 : `LastSurfaceRefused.Reset()` dans la seule branche où le couple bouge, rien d'autre | **PASS-STATIC** | `.cpp` l. 1447-1448 (dans `else if (CoupleSpotFree(…))`, l. 1439-1449, après la boucle de déplacement l. 1443-1446) ; diff I0 vers I1 = +2 / −0 (§ 4) |
| C1 | Consigne 1 : `TextBlock_1` invisible seulement si son texte commence par « Click any mouse button », sinon intact | **PASS-STATIC** | `.cpp` l. 1778-1792 (§ 3.1) |
| C2 | Consigne 2 : `SearchAction` ou parent introuvable : rien d'inséré, une ligne de témoin | **PASS-STATIC** pour le cas décrit ; **réserve D1** (`AddChild` rendant nul) | `.cpp` l. 1851-1858, 1889-1908 |
| C3 | Consigne 3 : texture `info` absente, un « i » en texte | **PASS-STATIC** | `.cpp` l. 1868-1884, 1906-1908 |
| C4 | Consigne 4 : ligne de la molette retirée si CAM ne déplace pas le couple à la molette | **PASS-STATIC** (règle respectée à la lettre) ; **observation D2** | `.cpp` l. 126, 1104, 1745-1746 ; `LPKeyboardSubsystem.cpp:165` ; § 3.1 |
| C5 | Consigne 5 : hors du sexe, rien ne change | **PASS-STATIC** ; **observation D3** | `.cpp` l. 1769, 1775, 1806-1811, 1820-1837 |
| C6 | Consigne 6 : aide en anglais, touches lues dans les mappages | **PASS-STATIC** | `.cpp` l. 295-317, 1743-1764 ; `DefaultInput.ini` l. 97, 118, 150-160 |
| E1 (IMPL § 3) | Icône en fin de rangée (`AddChild`), non juste après le champ | **PASS-STATIC**, écart **jugé acceptable** | `PanelWidget.h:68-72`, `PanelWidget.cpp:184-198` (§ 3.2) |
| E2 (IMPL § 3) | I-d par opacité de rendu et non par vidage du texte | **PASS-STATIC**, écart **jugé acceptable** | `TextBlock.cpp:469-478` : `SetText` fait `TextDelegate.Unbind()` |
| E3 (IMPL § 3) | Pas de lecture MCP : confirmation au premier essai | **reporté** V7 | aucune lecture possible ici |
| E4 (non annoncé) | La SPEC § 2.1 I-c demandait un `UBorder` et un `UTextBlock` UMG avec contour ; l'IMPL fait une boîte Slate (`SOverlay`, `SBorder`, `STextBlock`) avec ombre | **PASS-STATIC**, écart **non annoncé** (N6) | `.cpp` l. 1919-1955 ; même technique que `EnsureWidget` (l. 1682-1714) |
| CAM-86 | Ligne `icone inseree` (parent, classe), image `info` ; ligne `4 lignes d'origine masquees` | formats de lignes **PASS-STATIC** ; comportement **reporté** V7 | `.cpp` l. 1784, 1906-1908 |
| CAM-87 | Survol = info-bulle ; clic ouvre et ferme la boîte ; la fermeture du panneau la ferme | chemin de code **PASS-STATIC** ; comportement **reporté** V7 | `.cpp` l. 1814, 1886, 1912-1917, 1820-1837 |
| CAM-88 | Touches du clavier (Z/S, Q/D) ; suivent un remappage | **PASS-STATIC** (lecture à chaque 0,1 s) ; affichage **reporté** V7 | `.cpp` l. 1803, 1811-1814, 1828-1830 |
| CAM-89 | Hors sexe : icône absente du `Cursor`, texte de jouissance affiché | icône et boîte **PASS-STATIC** ; texte de jouissance : **observation D3**, **reporté** V7 | `.cpp` l. 1806, 1822-1825 |
| CAM-90 | Deux passages sur la même commode écrivent deux lignes (N2) | **PASS-STATIC** (code) ; **reporté** V7 | `.cpp` l. 1433-1437, 1448 |
| Compilation | 0 erreur, 0 avertissement nouveau sous V7 | **BLOCKED-compilation** (à BUILD) | aucune compilation lancée ici |
## 3. Critères rejoués (commande → résultat)
| # | Contrôle | Commande / lecture | Résultat brut | Verdict |
|---|---|---|---|---|
| 1 | MD5, taille, fins de ligne des 6 fichiers | `pwsh -NoProfile -File scratchpad\v2.ps1` (octets lus par `ReadAllBytes`, CR et LF comptés, sans `grep -c`) | final `.cpp` `size=85842 CR=0 LF=1969 MD5=73CC3BD667059A903E44405C5E793DE3`, `.h` `size=9813 CR=0 LF=179 MD5=6EDBBCCF61759B8C6EE4890DF9BC5FE7` ; I0 `.cpp` `73384 / 1687 / 210B665AA8D5D628B8C1AC429A339B74`, `.h` `8471 / 152 / 239DA6094E7401E0E3B3699A635A8150` ; I1 `.cpp` `73526 / 1689 / 3A11A3872378485DC64804AEC0A761EE`, `.h` `8471 / 152 / 239DA609…` | **PASS** : égalent la fiche et l'IMPL § 0 ; LF pur (CR=0), sans BOM (premiers octets `2f2f20`), dernier octet `0a` |
| 2 | Manifestes des instantanés | `Get-Content …\CAM-B2p-I0\manifest.csv` et `I1` | I0 : tailles 73384 / 8471 ; I1 : 73526 / 8471, MD5 identiques aux fichiers ; dossiers `Source\LifePlay\` + `manifest.csv`, dates 14:40:05 et 14:40:13 | **PASS** |
| 3 | Chiffres de l'IMPL recomptés (sk-recount) | Grep des 11 constantes, 9 includes, 6 fonctions, membres du `.h` l. 148-171 | 9 includes, 11 constantes, 3 déclarations avancées, 6 fonctions : **conformes**. Membres : `HelpCursor`, `HelpButton`, `FadedHelpLines`, `FadedLinesOpacity`, `bHelpBoxOpen`, `HelpTextShown`, `HelpRoot`, `HelpText`, `HelpViewport` = **9** ; l'IMPL § 2.2 dit « 10 membres » | **un écart** (N7), sans effet sur le code |
| 4 | Diff brut et répartition | `git diff --no-index -U0` puis `--numstat` (sorties dans § 4) | I0 vers I1 `.cpp` +2/−0 ; I1 vers final `.cpp` +280/−0, `.h` +27/−0 ; I0 vers final `.cpp` +282/−0, `.h` +27/−0 | **PASS** : la somme égale le global ; lignes 1687 + 282 = 1969 et 152 + 27 = 179 |
| 5 | Aucune ligne retirée ou modifiée dans B2p-C à B2p-H | lecture des 9 hunks (§ 4) : tous des ajouts purs, aucun retrait (numstat `0` retraits) | hunks aux l. 6-14, 189-202, 293-318, 629, 635-636, 1447-1448, 1742-1969 (`.cpp`) ; 23-25 et 148-171 (`.h`) | **PASS-STATIC** : aucune ligne de C à H n'est modifiée |
| 6 | `Tick` garde son comportement | `Read` l. 633-685 | `TickControlsHelp();` en l. 636, **avant** `ComputeLabel()` (l. 637) ; la fonction rend `void` ; `Tick` n'a aucun retour avant l. 636 ; les retours l. 662 et 666 sont les anciens | **PASS-STATIC** |
| 7 | Symboles uniques | Grep de 24 noms neufs sur `Source` | 90 occurrences, **seulement** dans `LPCameraModeSubsystem.cpp` (75) et `.h` (15) : aucun homonyme externe | **PASS** |
| 8 | Dépendances du module | `LifePlay.Build.cs` l. 17 et 38 | `InputCore` (public) ; `Slate`, `SlateCore`, `UMG` (privés, l. 38) ; `Engine` public | **PASS** |
| 9 | Includes suffisants | `.cpp` l. 5-37 contre les types employés | `WidgetTree.h`, `Button.h`, `CanvasPanelSlot.h`, `HorizontalBoxSlot.h`, `Image.h`, `PanelWidget.h`, `TextBlock.h`, `Texture2D.h`, `SBorder.h` ajoutés ; déjà présents : `UserWidget.h`, `InputSettings.h` (donc `PlayerInput.h` : `FInputActionKeyMapping`), `CoreStyle.h`, `SOverlay.h`, `STextBlock.h`, `GameViewportClient.h`, `SlateApplication.h`. Précédents du module pour les mêmes en-têtes : `LPKeyboardSubsystem.cpp:5`, `LPSettingsSubsystem.cpp:6,9,12,13` | **PASS-STATIC** |
| 10 | `.generated.h` et `UFUNCTION` privée | `.h` l. 15, 27-30, 36, 156-157 | `#include "LPCameraModeSubsystem.generated.h"` (l. 15, dernier include), `UCLASS()` + `GENERATED_BODY()` (l. 27-30), `private:` (l. 36), `UFUNCTION()` + `void ToggleControlsHelp();` (l. 156-157). `AddDynamic` et `AddUniqueDynamic` sont appelés **depuis une fonction membre** de la classe (l. 1848, 1886) : l'accès au membre privé est licite. `OnClicked` est un `DECLARE_DYNAMIC_MULTICAST_DELEGATE` (`Button.h:18, 76`) : les deux macros existent. Précédent du module : `LPKeyboardSubsystem.cpp:599` (`AddDynamic`, fonction publique). UHT génère le thunk et `friend struct …_Statics` pour une fonction privée (usage courant du moteur, **estimé**, sans exemple relu) | **PASS-STATIC** (UHT et compilation : BUILD) |
| 11 | Unités compilées | Grep de `Module.LifePlay.cpp` (UnrealGame Development, Shipping, UnrealEditor Development) | `LPCameraModeSubsystem.cpp` est **absent** des trois unités unifiées ; seul `UHT/LPCameraModeSubsystem.gen.cpp` y figure (UnrealGame Development, l. 6) : le `.cpp` se compile seul quand il est modifié | **PASS** : aucune collision d'unité avec un autre `.cpp` ; BUILD doit vérifier que le module figure dans les unités compilées |
| 12 | Texte d'aide : `Printf` | lecture l. 1748-1763 ; moteur `Platform.h:1322, 1332, 1348` ; précédent moteur `ShaderCompiler.cpp:5978-5979` | **8** `%s` (l. 1750 : 1 ; l. 1751 : 4 ; l. 1756 : 2 ; l. 1758 : 1) et **8** arguments (l. 1759 : 1 ; 1760 : 2 ; 1761 : 2 ; 1762 : 2 ; 1763 : 1), dans l'ordre : `SwitchCamera`, `MoveForward` +1 et −1, `MoveRight` −1 et +1, `LeftRight`, `DownUp`, `Pause`. `TEXT(x)` vaut `WIDETEXT(x)` = `L ## x` : `L"…" "…"` est une concaténation licite (même idiome que `ShaderCompiler.cpp:5978-5979`) ; texte entièrement en ASCII | **PASS-STATIC** |
| 13 | Formats `UE_LOG` | lecture l. 1784, 1791, 1855-1857, 1862-1863, 1906-1908, 1915 | 1/1, 0/0, 2/2 (`%s` conditionnel de deux tableaux : décroissent en `const TCHAR*`), 2/2, 4/4 (`%s`, `%s`, `%d` entier de `GetChildrenCount`, `%s`), 1/1 | **PASS-STATIC** |
| 14 | Non-ASCII hors commentaires | Grep `[^\x00-\x7F]` sur le `.cpp` | 10 lignes (83, 155, 189, 543, 585, 1157, 1380, 1663, 1745, 1878), **toutes des commentaires** (189, 1745 et 1878 sont neuves ; les 7 autres existaient dans le fichier déjà compilé) ; aucun `TEXT(…)` non ASCII | **PASS** |
| 15 | Fichiers STG / STE | `Get-ChildItem … Sort LastWriteTime` | `LPCameraModeSubsystem.cpp` 14:43:20, `.h` 14:43:12 ; puis `LPShowCoupleSubsystem.*` 12:43:20 ; `LPOutdoorRoomSubsystem.*` et `LPDirectControlSubsystem.*` 12:05:58 ; aucun autre fichier de `Source` touché après 12:43 | **PASS** (par les dates) |
| 16 | État git de la copie | `git status --short -- Source Docs/Migration/etat/CAM Config` | `M` : `LPDebugSubsystem`, `LPDirectControlSubsystem`, `LPOutdoorRoomSubsystem`, `LPShowCoupleSubsystem`, `LifePlay.cpp` (autres lots) ; `??` : `LPCameraModeSubsystem.*`, `LPBonusRewardSubsystem.*`, `LPSaveRelaunch.*` | **PASS** : aucun fichier inattendu pour ce lot |
### 3.1 Les six consignes de Big Boss, une à une
1. **`TextBlock_1` invisible seulement si son texte commence par « Click any mouse button »** — `.cpp` l. 1775-1792. `SexUI` = `GI.SexInterface` (dump du PC l. 3030-3038 : `VariableGet_108 GI` vers `VariableGet_109 SexInterface` vers `TextBlock_1`) ; `Lines->GetText().ToString().StartsWith("Click any mouse button", CaseSensitive)` (l. 1778). Opacité à 0 **seulement si** `bOldHelp` et opacité > 0 (l. 1779-1785) ; sinon rien, sauf le retour de l'opacité si **ce même bloc** a été masqué par ce code et porte un autre texte (l. 1786-1792). Un bloc dont le texte ne commence pas par la phrase n'est jamais modifié. `UTextBlock::GetText()` rend le texte du widget Slate quand il existe (donc la **liaison évaluée**), sinon `Text` (`TextBlock.cpp:457-465`) ; `SetText` appelle `TextDelegate.Unbind()` (`TextBlock.cpp:469-472`) : **choisir l'opacité de rendu, qui laisse la liaison, est correct**. `UWidget::SetRenderOpacity` met à jour la propriété **et** le widget Slate (`Widget.cpp:454-463`) ; `GetRenderOpacity` lit le widget Slate (`Widget.cpp:443-452`). **PASS-STATIC.**
2. **`SearchAction` ou son parent introuvable : rien d'inséré, une ligne** — `.cpp` l. 1851-1858 : `SearchBox` nul, `SearchParent` nul ou `WidgetTree` nul : `UE_LOG` « icone non inseree, <SearchAction | le parent de SearchAction> introuvable dans <nom> » puis `return nullptr` **avant** tout `ConstructWidget` : aucun widget n'est créé. L'ancrage étiré dans un `CanvasPanel` est refusé de la même façon (l. 1859-1865 ; `Anchors.h:78, 81` : `IsStretchedVertical`, `IsStretchedHorizontal`). **PASS-STATIC** ; **réserve D1** : le cas où `AddChild` rend nul (parent à un seul enfant) n'est pas traité (l. 1889).
3. **Texture `info` absente : un « i »** — `LoadObject<UTexture2D>(nullptr, "/Game/Heavy/New/info.info")` (l. 1868) ; si nul : `UTextBlock` « i », police `Bold` 18, blanc (l. 1877-1884) ; sinon `UImage`, `SetBrushFromTexture`, `SetDesiredSizeOverride(28, 28)` (l. 1871-1876 ; `Image.h:81, 108`). La ligne d'insertion nomme l'image (« absente, lettre i », l. 1906-1908). **PASS-STATIC.**
4. **Ligne de la molette retirée si CAM ne déplace pas le couple à la molette** — `bSexWheelZoom = false` (`.cpp` l. 126, constante `const bool`). Seul lecteur de `ScrollDownUp` dans `Source/LifePlay` : `.cpp` l. 1104, `Wheel = bSexWheelZoom ? AxisValue(PC, AxisScroll) : 0.f` ; `Wheel` n'entre ensuite que dans des tests contre zéro (l. 1117, 1130) et un terme multiplié (l. 1132), donc **nul**. L'autre occurrence (`LPKeyboardSubsystem.cpp:165`) range le nom d'axe dans la catégorie « Camera » du menu Keyboard : elle ne lit aucune valeur. Aucune ligne « Mouse wheel » dans le texte (l. 1748-1758). **PASS-STATIC.** **Observation D2** : la règle est respectée à la lettre, mais **Room** (Blueprint, hors C++) monte et descend bien le couple avec **Maj + molette** (`CAM-B2p-E-IMPL.md:48` : « Maj + molette avait monté le couple … » ; `CAM-B2p-G-SPEC.md:13` : « Z de 100 à 127 (molette …) » ; `CAM-B2p-F-VAL.md:64`) : l'aide omet un contrôle réel. Décision pour Big Boss.
5. **Hors du sexe, rien ne change** — `bSexOn` (l. 1769) gouverne : `SexUI` nul hors sexe (l. 1775), donc `TextBlock_1` n'est ni lu ni écrit ; l'insertion exige `bSexOn` (l. 1798) ; l'icône déjà insérée passe en `Collapsed` (l. 1806, 1807-1810) ; `HelpBody` est vide (l. 1803) ; `bPanelOpen` est faux (l. 1821) donc `bHelpBoxOpen = false` (l. 1822-1825) et la boîte est repliée (l. 1834-1837). **PASS-STATIC.** **Observation D3** : l'opacité 0 posée pendant le sexe n'est **pas** rendue à la fin du sexe (le code de retour d'opacité est lui aussi derrière `bSexOn`).
6. **Aide en anglais, touches lues dans les mappages** — texte en anglais (l. 1748-1758) ; touches par `MappedKeys` (l. 295-317), lu à chaque appel dans `UInputSettings` (même source que `AxisValue`, l. 272-291). Valeurs attendues avec `DefaultInput.ini` : `SwitchCamera` = `C` (l. 118) ; `MoveForward` +1 = `Z`, `W`, −1 = `S` (l. 155-157) ; `MoveRight` −1 = `Q`, `A`, +1 = `D` (l. 158-160) ; `LeftRight` = `Right`, `Left` (l. 150-151) ; `DownUp` = `Up`, `Down` (l. 152-153) ; `Pause` = `SpaceBar` (l. 97). **PASS-STATIC.**
### 3.2 Contrôles de lecture, fonction par fonction
- **`MappedKeys`** (l. 295-317) : signes de l'axe : `Sign == 0 || (Sign > 0 ? Scale > 0 : Scale < 0)` (l. 302) ; avec `DefaultInput.ini`, `MoveForward` donne `Z/W` pour +1 et `S` pour −1, `MoveRight` donne `Q/A` pour −1 et `D` pour +1 : l'ordre des paramètres de `Printf` (l. 1760-1761) est « avant, arrière, gauche, droite », comme le texte « closer / away » et « orbit ». Sens de l'axe : en mode Scène, `Distance - Forward * SexDollySpeed * Step` (l. 1132) : `Forward` > 0 rapproche, donc « closer » = `MoveForward` +1 (établi). Filtres `!IsGamepadKey() && !IsMouseButton() && !IsAxis1D()` (l. 303, 310) : `FKey` les définit (`InputCoreTypes.h:72-78`) ; `GetDisplayName(false)` rend un `FText` (`InputCoreTypes.h:89`), puis `.ToString()` ; `AddUnique` écarte les doublons ; repli « ? » (l. 316). Action : `FInputActionKeyMapping::ActionName`, `Key` (`PlayerInput.h:148-174`) ; `GetActionMappings()` et `GetAxisMappings()` non dépréciées (`InputSettings.h:316-318`). **PASS-STATIC.** Non filtré : `Key.IsValid()` (D6, négligeable).
- **`BuildControlsHelp`** (l. 1743-1764) : `const`, n'écrit rien ; contrôle 12. **PASS-STATIC.**
- **`TickControlsHelp`** (l. 1766-1842) :
- garde `bSexOn` (l. 1769) sur l'insertion (l. 1798), l'aide (l. 1803), la visibilité (l. 1806) ;
- opacité gardée (`FadedLinesOpacity`, l. 1781) et rendue (l. 1789) ;
- `PC.CursorREF` : `ReadObject(PC, "CursorREF")` (l. 1797 ; dump du PC l. 3012-3017, 1006-1007) ;
- **une tentative par instance** : `HelpCursor` est affecté **avant** l'appel (l. 1800-1801), comparé par pointeur faible (l. 1798) : une instance ratée n'est pas retentée ;
- visibilité : `Visible` en sexe, `Collapsed` hors sexe, écrite seulement si elle change (l. 1806-1810) ;
- info-bulle : `SetToolTipText` quand le texte change (l. 1811-1814 ; `Widget.cpp:526-536` met à jour le widget Slate vivant) ;
- **test du panneau comme `IfThenElse_10` du PC** : dans le dump, `IfThenElse_10.Condition` vient de `CallFunction_79`, `Widget::IsVisible()` sur `CursorREF.BelowCursor` (l. 3015-3023) ; le code fait `PanelArea->IsVisible()` sur le même widget (`GetWidgetFromName("BelowCursor")`, l. 1820-1821), et `UWidget::IsVisible` lit la visibilité du widget Slate (`Widget.cpp:395-404`) : **même fonction, même résultat**. La fermeture par le PC (`ExitSexActions`, dump l. 1163-1170) met `BelowCursor` en `Collapsed` : `bPanelOpen` devient faux et la boîte se ferme ;
- boîte fermée avec le panneau (l. 1822-1825, 1834-1837) ; `HelpText` et `HelpRoot` sont valides quand `EnsureHelpBox()` rend vrai (l. 1919-1931). **PASS-STATIC.**
- **`AttachHelpIcon`** (l. 1844-1910) : `ActionCursor->WidgetTree` est un `TObjectPtr<UWidgetTree>` public (`UserWidget.h:1524`) ; `ConstructWidget<UButton>(UButton::StaticClass(), nom)` (`WidgetTree.h:106` ; `TSubclassOf(UClass*)` implicite, `SubclassOf.h:33` ; précédent `LPBonusRewardSubsystem.cpp:621` dans le même `Cursor`). Noms `LPControlsHelp` (l. 1866), `LPControlsHelpIcon`, `LPControlsHelpLetter` : une instance neuve a un arbre neuf (pas de collision) ; une icône déjà présente est reprise par nom avant (l. 1846-1850). **`AddChild`, pas `InsertChildAt`** : `InsertChildAt` appelle `AddChild` puis `ShiftChild`, qui ne réordonne que `Slots` ; l'en-tête le dit (« This does not update the live slate version, it requires a rebuild of the whole UI », `PanelWidget.h:69-72`, `PanelWidget.cpp:184-198`). `AddChild` passe par `OnSlotAdded`, qui construit le slot Slate **vivant** pour `HorizontalBox` (`HorizontalBox.cpp:32-39`) et `CanvasPanel` (`CanvasPanel.cpp:33-40`). L'icône vient donc en fin de rangée : l'écart E1 est cohérent. Slots : `UHorizontalBoxSlot::SetVerticalAlignment`, `SetPadding` (`HorizontalBoxSlot.h:48, 63`) ; `UCanvasPanelSlot::GetAnchors/GetPosition/GetSize/GetAlignment/GetZOrder` const et leurs `Set…` (`CanvasPanelSlot.h:104-156`) ; `UButton::SetBackgroundColor(FLinearColor)` (`Button.h:129`) ; `UContentWidget::SetContent` (`ContentWidget.h:23`) ; `UWidget::Slot` public (précédent `LPBonusRewardSubsystem.cpp:694`). **PASS-STATIC**, **sauf D1** (`AddChild` nul) ; autres types de panneau (`WrapBox`, `ScrollBox`) : l'icône est ajoutée sans réglage de slot (acceptable).
- **`ToggleControlsHelp`** (l. 1912-1917) : `UFUNCTION()` (`.h` l. 156) ; bascule `bHelpBoxOpen`, une ligne, puis `TickControlsHelp()`. `TickControlsHelp` n'appelle jamais `ToggleControlsHelp` ni ne déclenche `OnClicked` : **récursion d'un seul niveau, sans boucle**. Le clic peut survenir pendant un traitement Slate : `TickControlsHelp` y crée au plus la boîte du viewport (`AddViewportWidgetContent`), ce que fait déjà `EnsureWidget` depuis un ticker. **PASS-STATIC.**
- **`EnsureHelpBox` / `RemoveHelpBox`** (l. 1919-1969) : même cycle de vie que `EnsureWidget` / `RemoveWidget` (l. 1682-1730) : retour vrai si la racine existe et le viewport est le même, sinon `RemoveHelpBox()` puis nouvelle racine ; `AddViewportWidgetContent(…, HelpZOrder = 60)` ; `RemoveViewportWidgetContent`. `RemoveHelpBox()` est appelé dans `Deinitialize` (l. 629, juste après `RemoveWidget()`). `FCoreStyle::Get().GetBrush("WhiteBrush")` : la brosse existe dans `StarshipCoreStyle.cpp:213` et `CoreStyle.cpp:301` (`FSlateColorBrush` blanc, teinte par `BorderBackgroundColor` à 75 %). **PASS-STATIC.**
### 3.3 API du moteur 5.8 lues (`D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime`)
| API | Source lue | Résultat |
|---|---|---|
| `UTextBlock::GetText() const`, `SetText(FText)`, liaison | `UMG/Private/Components/TextBlock.cpp:457-465, 469-483` | `GetText` : texte du widget Slate (liaison évaluée) sinon `Text` ; `SetText` délie `TextDelegate` |
| `UWidget::GetRenderOpacity() const`, `SetRenderOpacity(float)`, `SetToolTipText(const FText&)`, `IsVisible() const`, `GetVisibility() const` | `UMG/Public/Components/Widget.h:556, 582, 586, 598, 602` ; `Widget.cpp:395-404, 443-463, 526-536` | conformes |
| `UButton::SetBackgroundColor(FLinearColor)`, `OnClicked` | `Button.h:18, 76, 129` | conformes ; dynamique multicast |
| `UImage::SetDesiredSizeOverride(FVector2D)`, `SetBrushFromTexture(UTexture2D*, bool)` | `Image.h:81, 108` | conformes (la variante dépréciée est `SetBrushSize`, non utilisée) |
| `UPanelWidget::AddChild(UWidget*)` ; `InsertChildAt` ; rend nul si le panneau est à enfant unique et non vide | `PanelWidget.h:59-79` ; `PanelWidget.cpp:132-142, 184-198` | conformes ; **le retour nul existe** (D1) |
| `FAnchors::IsStretchedHorizontal/Vertical() const` | `Slate/Public/Widgets/Layout/Anchors.h:78, 81` | conformes |
| `FKey::IsAxis1D/IsMouseButton/IsGamepadKey/IsDigital/GetDisplayName(bool)` | `InputCore/Classes/InputCoreTypes.h:72-89` | conformes |
| `FInputActionKeyMapping`, `FInputAxisKeyMapping` ; `UInputSettings::GetActionMappings/GetAxisMappings` | `Engine/Classes/GameFramework/PlayerInput.h:148-174, 216-230` ; `InputSettings.h:316-318` | conformes |
| `FCoreStyle` « WhiteBrush » | `SlateCore/Private/Styling/StarshipCoreStyle.cpp:213`, `CoreStyle.cpp:301` | présente |
| `UUserWidget::WidgetTree`, `GetWidgetFromName(const FName&) const` ; `UWidgetTree::ConstructWidget` | `UserWidget.h:1426, 1524` ; `WidgetTree.h:106` | conformes |
| `TSubclassOf(UClass*)` | `CoreUObject/Public/Templates/SubclassOf.h:33` | implicite |
### 3.4 Compilation probable sous `/we4456 /we4458 /we4459`
- **C4456 / C4457 (locaux englobants)** : chaque fonction neuve relue en entier. `TickControlsHelp` : `GI`, `bSexOn`, `World`, `PC`, `SexUI`, `Lines` (portée du `if`), `bOldHelp`, `ActionCursor`, `HelpBody`, `HelpIcon` (portée du `if`), `IconVisibility`, `PanelArea`, `bPanelOpen` : tous distincts. `AttachHelpIcon` : paramètre `ActionCursor`, locaux `Existing` (portée du `if`), `SearchBox`, `SearchParent`, `SearchCanvasSlot`, `IconButton`, `IconTexture`, `IconImage`, `IconLetter` (blocs distincts), `IconSlot`, `RowSlot`, `CanvasSlot` : distincts. `EnsureHelpBox` : `GI`, `ViewportClient`, `NewHelpText`, `NewHelpRoot`. `RemoveHelpBox` : `ViewportClient` (portée du `if`). `MappedKeys` : paramètres `Name`, `Sign`, locaux `Names`, `Settings`, `Map` (deux boucles, portées distinctes), `bSign`. `BuildControlsHelp` : une déclaration `using` (pas de variable). **Aucun doublon.**
- **C4458 (membres)** : `.h` entier relu (l. 1-179). Membres de la classe : `TickHandle`, `SwitchKeyHandle`, `bSwitchKeyDown`, `LastEmptyView`, `WitnessCooldown`, `OcclusionCooldown`, `bCharGenSeen`, `CharGenRepairTimeout`, `C1Cooldown`, `bMenuOnScreen`, `SexKeysCooldown`, `CoupleGrabMode`, `bLeftWasDown`, `bRightWasDown`, `bGrabMoved`, `bGrabHasHeight`, `GrabHeight`, `GrabOffset`, `GrabMouseStart`, `GrabLastMouseX`, `GrabTurned`, `GrabCamera`, `GrabCameraLocation`, `GrabCameraRotation`, `bCursorWarned`, `LastCouplePivot`, `bHasLastPivot`, `bHiddenRoomWarned`, `CoupleBoundsCooldown`, `CoupleMoveCooldown`, `bGrabJustEnded`, `bBoundsLogged`, `LastSpotBlocker`, `LastSurfaceRefused`, `bSceneFocusChecked`, `bFocusClickWasDown`, `GrabLeadStart`, `LastSexView`, `bSexC3Was`, `bMiddleWasDown`, `bMiddleHadMouse`, `bMiddleMoved`, `MiddleHeld`, `MiddleStart`, les 9 membres neufs, `Root`, `Text`, `Viewport`, `ShownLabel`, `bShown`. **Aucun nouveau local ni paramètre ne porte l'un de ces noms** (les locaux `HelpBody`, `HelpIcon` diffèrent de `HelpText`, `HelpButton`).
- **C4459 (portée globale ou espace anonyme)** : les constantes et `MappedKeys` sont dans `LPCameraModeLocal` (espace nommé, l. 42). Grep, sur `Source/LifePlay` hors CameraMode, des 25 noms locaux neufs (`^\t?[A-Za-z_].*\b(Lines|Existing|CanvasSlot|Names|Sign|bSign|RowSlot|IconSlot|PanelArea|HelpBody|HelpIcon|SexUI|bOldHelp|bPanelOpen|IconVisibility|SearchBox|SearchParent|SearchCanvasSlot|IconButton|IconTexture|IconImage|IconLetter|ActionCursor|NewHelpText|NewHelpRoot)\b`) : hits `Existing` (`LPDebugSubsystem.cpp:257`, paramètre de fonction), `CanvasSlot` (`LPBonusRewardSubsystem.cpp:695-713`, local), `Names` (`LPDirectControlSubsystem.cpp:1023`, `LPSessionLogSubsystem.cpp:321`, `LPShowCoupleSubsystem.cpp:585` : locaux, les deux extrêmes relus aux l. 575-588 et 1012-1025), `Lines` (`LPHairstyleSubsystem.cpp:284`, `LPKeyboardSubsystem.cpp:769`, `LPModGuard.cpp:515`, `LPModInfoSubsystem.cpp:221` et `:385`, `LPModInfoSubsystem.h:58` : locaux ou paramètres). **Aucune déclaration de portée globale.** Les noms de membres, fonctions et constantes neufs (grep de 24 noms) : 90 occurrences, toutes dans CameraMode. `CameraMode.cpp` n'est de toute façon pas dans une unité unifiée (contrôle 11).
- **Types** : `UObject* ReadObject(const UObject*, FName)` (l. 210) reçoit `GI` et `PC` ; ternaires `X* : nullptr` sans conversion ambiguë ; `TWeakObjectPtr<UButton/UTextBlock/UUserWidget>` déclarés avec des classes avancées (précédent : `TWeakObjectPtr<UGameViewportClient>` avec `UGameViewportClient` avancé, `.h` l. 22 et 175) et définies dans le `.cpp` (l. 5-13) ; `SNew(SOverlay) … + SOverlay::Slot() … [ SNew(SBorder) … [ SAssignNew(…) ] ]` de même forme que l. 1697-1706 ; `FString::Join(TArray<FString>, const TCHAR*)` et ternaire `FString`/`FString(TEXT("?"))` de même type. **Aucune erreur de type trouvée.**
- **Résidu (N8)** : un nom (`Names`, `Lines`, `Sign`, `Settings`, `Map`) déclaré en portée globale par un en-tête moteur n'est excluable que par compilation ; improbable (`Settings` et `Map` existent déjà comme locaux compilés, l. 274, 280). À trancher par BUILD.
## 4. Diff lu en entier
Chaque diff a été lu en entier (`git diff --no-index -U3`, deux diffs `.cpp` et un diff `.h` ; `-U0` et `--numstat` pour les comptes).
| Diff | `.cpp` ajoutées / retirées | `.h` ajoutées / retirées | Lignes avant vers après (LF) |
|---|---|---|---|
| I0 vers I1 | **+2 / −0** (`numstat` : `2 0` ; `-U0` : +2 −0) | 0 (diff vide) | `.cpp` 1687 vers 1689 |
| I1 vers final | **+280 / −0** (`numstat` : `280 0`) | **+27 / −0** (`numstat` : `27 0`) | `.cpp` 1689 vers 1969 ; `.h` 152 vers 179 |
| I0 vers final (global) | **+282 / −0** | **+27 / −0** | `.cpp` 1687 vers 1969 ; `.h` 152 vers 179 |
Sommes : 2 + 280 = 282 (`.cpp`) et 0 + 27 = 27 (`.h`), égales au global ; 1687 + 282 = 1969 et 152 + 27 = 179, égales aux lignes comptées (contrôle 1).
Hunks de I1 vers final (lignes de l'état final) :
| Fichier | Lignes | Contenu | Point |
|---|---|---|---|
| `.cpp` | 6-14 (+9) | 9 includes | I1 |
| `.cpp` | 189-202 (+14) | 11 constantes d'aide, commentaires, ligne vide | I1 |
| `.cpp` | 293-318 (+26) | `MappedKeys` | I1 |
| `.cpp` | 629 (+1) | `RemoveHelpBox();` dans `Deinitialize` | I1 |
| `.cpp` | 635-636 (+2) | commentaire et `TickControlsHelp();` en tête de `Tick` | I1 |
| `.cpp` | 1742-1969 (+228) | `BuildControlsHelp`, `TickControlsHelp`, `AttachHelpIcon`, `ToggleControlsHelp`, `EnsureHelpBox`, `RemoveHelpBox` | I1 |
| `.h` | 23-25 (+3) | `class UButton; class UTextBlock; class UUserWidget;` | I1 |
| `.h` | 148-171 (+24) | 6 fonctions dont l'`UFUNCTION`, 9 membres | I1 |
| `.cpp` (I0 vers I1) | 1447-1448 (+2) | commentaire `VAL B2p-H N2` et `LastSurfaceRefused.Reset();` | I0 |
9 + 14 + 26 + 1 + 2 + 228 = 280 ; 3 + 24 = 27. **Aucun ajout non demandé** : chaque hunk se rattache à I0 ou I1 de la fiche. **Non-régression B2p-C à B2p-H** : zéro ligne retirée, zéro ligne modifiée ; les fonctions de C à H (`TickSexCamera`, `TickCoupleMouse`, `TickCoupleBounds`, `TickSceneCamera`, `TickPanelClick`, `CoupleSpotFree`, `ComputeLabel`) ne reçoivent que le `Reset()` de I0 (l. 1448). `TickSexCamera` garde `bSexWheelZoom` (l. 1104) et l'appel de `TickPanelClick` (l. 1095).
## 5. Verdict
| Point | Verdict |
|---|---|
| **I0** (N2) | **PASS-STATIC** : `Reset()` dans la seule branche où le couple bouge, rien d'autre (§ 2, § 4) |
| **I1**, consigne 1 | **PASS-STATIC** |
| **I1**, consigne 2 | **PASS-STATIC**, réserve **D1** |
| **I1**, consigne 3 | **PASS-STATIC** |
| **I1**, consigne 4 | **PASS-STATIC**, observation **D2** |
| **I1**, consigne 5 | **PASS-STATIC**, observation **D3** |
| **I1**, consigne 6 | **PASS-STATIC** |
| **I1**, `MappedKeys`, `BuildControlsHelp`, `TickControlsHelp`, `AttachHelpIcon`, `ToggleControlsHelp`, `EnsureHelpBox`/`RemoveHelpBox` | **PASS-STATIC** (§ 3.2) |
| Contrôles transverses (types, includes, dépendances, C4456/C4458/C4459 par lecture, non-régression) | **PASS-STATIC** |
| **Global** | **PASS-STATIC** : aucun défaut bloquant. **Non couvert** : la compilation (**BLOCKED-compilation**, à confier à BUILD) et tout comportement en jeu (reporté V7). |
## 6. Points à confirmer par l'utilisateur (cases)
- [ ] BUILD : `LifePlay` compile avec 0 erreur et 0 avertissement nouveau sous V7 ; `LPCameraModeSubsystem.cpp` doit figurer dans les unités compilées ; UHT régénère `LPCameraModeSubsystem.generated.h` avec `execToggleControlsHelp` (première `UFUNCTION` de la classe).
- [ ] Décision sur D1 : faire traiter `AddChild` nul avant V7 (correction de quelques lignes, par l'auteur) ou laisser l'essai CAM-86 trancher ; sans correction, la ligne « icone inseree » peut être écrite alors que rien n'est inséré.
- [ ] Décision sur D2 : garder l'aide sans la ligne « Maj + molette » (règle de Big Boss à la lettre) ou ajouter « Shift + mouse wheel: raise / lower the couple » (contrôle de Room, établi par COMMUN-7).
- [ ] CAM-86 : ligne `Aide sexe : icone inseree dans … (parent, classe, n enfants), image …` ; icône à côté du champ de filtre ou des boutons, même image que FAQ ; ligne `4 lignes d'origine masquees` ; les 4 lignes disparues.
- [ ] CAM-87 : le survol montre l'info-bulle ; le clic ouvre et ferme la boîte ; la boîte est au bon endroit (la SPEC voulait « au même endroit que les 4 lignes » ; le code la met en bas à droite, marge basse de 96 px, l. 1936) ; la fermeture du panneau la ferme ; un clic gauche sur l'icône ne déclenche ni prise du couple ni bascule du panneau.
- [ ] CAM-88 : touches affichées (`Z/W`, `S`, `Q/A`, `D`, `Right/Left`, `Up/Down`, `SpaceBar`, `C`), suivant un remappage fait par le menu Keyboard.
- [ ] CAM-89 : hors du sexe, l'icône est absente du `Cursor` et la boîte fermée ; texte de jouissance de `TextBlock_1` visible s'il apparaît (D3).
- [ ] CAM-90 (N2) : deux passages sur la même commode écrivent deux lignes `surface refusee`.
- [ ] Accepter l'écart non annoncé E4 (boîte Slate au lieu de `UBorder`/`UTextBlock`, ombre au lieu de contour).
Tours : 37 / 40
## Constats
### Défauts bloquants
Aucun (preuves : contrôles 1 à 16, § 3.1 à 3.4 et lecture point par point).
### Défauts non bloquants
| # | Lieu | Constat | Niveau de preuve | État |
|---|---|---|---|---|
| D1 | `.cpp` l. 1889-1909 | `UPanelWidget::AddChild` rend `nullptr` quand le parent de `SearchAction` est un panneau à enfant unique déjà occupé (`PanelWidget.cpp:139-142`, `bCanHaveMultipleChildren` faux). `IconSlot` est alors nul : aucun réglage de slot, mais le journal écrit quand même « icone inseree dans … » (l. 1906-1908) et la fonction rend le bouton orphelin, que `TickControlsHelp` traite comme une icône (l. 1801, 1804-1810). La consigne 2 de Big Boss (« ne rien insérer et écrire une ligne de témoin ») est tenue pour les cas qu'elle nomme (`SearchAction` ou parent introuvable), non pour celui-ci ; l'essai CAM-86 peut passer à tort sur la seule ligne. Correction possible : `if (!IconSlot)` vers une ligne « non inseree, <classe> n'accepte pas d'enfant » et `return nullptr`. | établi (lecture du code et du moteur) ; probabilité en jeu : estimée faible (DIAG § 1.2 : le parent est probablement `HorizontalBox_0` ou `WrapBox`) | **levé** par la reprise D1 (voir la section « Reprise D1 » en fin de document) |
| D2 | `.cpp` l. 1745-1758 | L'aide ne cite pas la molette (règle de Big Boss respectée : CAM ne la lit plus, l. 126 et 1104). Mais Room monte et descend le couple avec **Maj + molette** (`CAM-B2p-E-IMPL.md:48`, `CAM-B2p-G-SPEC.md:13`, `CAM-B2p-F-VAL.md:64`) : contrôle réel absent de l'aide. La voie d'origine « Maj + bouton tenu + souris » (SPEC § 1.6, dernière ligne) est absente aussi (omission de la SPEC § 2.3). | établi (documents) ; absence de lecture de la molette par le C++ : établi (Grep) | ouvert, décision de Big Boss |
| D3 | `.cpp` l. 1775-1792 | L'opacité 0 de `TextBlock_1` est posée en sexe et n'est jamais rendue hors sexe : `SexUI` est nul quand `bSexOn` est faux, donc la branche de restitution (l. 1786-1792) ne s'exécute pas. Si `GI.SexOn` passe à faux avant que `TextBlock_1` affiche un autre texte (jouissance), ce texte reste invisible. Le code ne touche à rien hors sexe, ce qui respecte la consigne 5. Le texte de jouissance n'est montré qu'en sexe (DIAG § 1.1, **estimé fort**). | estimé (lecture) ; essai : CAM-89 | ouvert |
| D4 | `.cpp` l. 1798-1802 | Une seule tentative par instance de `Cursor` (consigne 2) : si `PC.CursorREF` est déjà non nul mais que son arbre n'est pas encore complet, l'instance est consommée sans retenter. Conforme à la consigne ; un témoin « introuvable » au premier essai révélerait le cas. | estimé | ouvert, négligeable |
| D5 | `.cpp` l. 1803, 1859-1865 | `BuildControlsHelp()` (8 parcours des mappages, allocations de `FString`) est appelée à chaque passage de `TickControlsHelp`, soit toutes les 0,1 s en sexe : coût négligeable. L'ancrage étiré est testé seulement quand `SearchAction` est dans un `CanvasPanel` (`Cast<UCanvasPanelSlot>` nul ailleurs : aucun refus). | établi | ouvert, cosmétique |
| D6 | `.cpp` l. 303, 310, 1762 | `MappedKeys` ne filtre pas `Key.IsValid()` ; l'ordre des touches suit `DefaultInput.ini` : `LeftRight` donne `Right/Left`, `DownUp` donne `Up/Down` (« Right/Left, Up/Down: move the couple »), lecture un peu inversée. | établi | ouvert, cosmétique |
| N5 | IMPL | Écart de gabarit (§ 1) : tête commune, « Ce qui n'a pas été fait », `Tours :` absents ; numérotation 0 à 5. | établi | non bloquant, à reporter à l'auteur |
| N6 | IMPL § 3 | L'écart E4 (boîte Slate au lieu de `UBorder`/`UTextBlock` de la SPEC § 2.1 I-c) figure en § 2.2 mais pas en § 3 « Écarts à la SPEC ». | établi | non bloquant |
| N7 | IMPL § 2.2 | « 10 membres » : le `.h` en a 9 (l. 161-171). Les autres chiffres (9 includes, 11 constantes, 6 fonctions, MD5, lignes) sont exacts. | établi | non bloquant |
| N8 | compilation | Résidu C4459 d'en-têtes moteur : non excluable sans compilation (voir § 3.4). | estimé (faible probabilité) | à trancher par BUILD |
### Non mesuré
Compilation et avertissements ; UHT (`execToggleControlsHelp`, première `UFUNCTION` de la classe) ; tout essai en jeu (CAM-86 à CAM-90, reportés V7) ; parent réel de `SearchAction` dans `Cursor_C` et classe de ce parent (aucune lecture MCP, décision de Big Boss) ; texte réel de `TextBlock_1` (les 4 lignes sont dans un seul bloc : DIAG « estimé fort ») ; si `GI.Cursor` et `PC.CursorREF` désignent la même instance (le code n'emploie que `PC.CursorREF`, comme le PC) ; position visuelle de la boîte d'aide et de l'icône ; rendu des touches (`GetDisplayName(false)` : noms exacts de `Z`, `W`, `SpaceBar`, `Right`…) ; évaluation de la liaison `Get_TextBlock_1_Text` appelée aussi depuis le ticker de CAM (une évaluation de plus toutes les 0,1 s, estimée sans effet) ; `LPTestStringCompat` et porte G-U3 (sans objet : ni chaîne de compatibilité, ni configuration, ni compilation dans ce lot).
### Fichiers à commiter (VAL)
A `Docs/Migration/etat/CAM/CAM-B2p-I-VAL.md` (dépôt du jeu, branche `ue5.8`). Fichiers modifiés par la VAL : ce livrable seulement ; fichiers de travail dans le dossier temporaire de session (`scratchpad` : `v1.ps1`, `v2.ps1`, `v3.ps1`, `I*vs*.diff`), hors dépôt.
### À verser
- `lp-validate` § 1 : les instantanés de `Snapshot.ps1` ont la forme `<instantané>\Source\LifePlay\<fichier>` (chemin relatif conservé) plus `manifest.csv` ; un script de diff doit viser ce sous-dossier (échec constaté avec `v1.ps1`).
- `lp-validate` § 1 : le crochet `ecriture-gardien` refuse aussi une redirection `>` utilisée pour dériver un script par `sed` ; écrire le script neuf avec Write dans le dossier temporaire.
- `lp-ue-compat` / `lp-build` : `UPanelWidget::AddChild` rend `nullptr` pour un panneau à enfant unique non vide (`PanelWidget.cpp:139-142`) et `InsertChildAt` ne réordonne pas le Slate vivant (`PanelWidget.h:69-72`) ; `UTextBlock::SetText` délie la liaison (`TextBlock.cpp:469-472`) ; `UWidget::IsVisible` lit le widget Slate lui-même (`Widget.cpp:395-404`) ; `FCoreStyle` possède « WhiteBrush » (`StarshipCoreStyle.cpp:213`) ; `TEXT("a" "b")` est licite (`ShaderCompiler.cpp:5978-5979`).
## Reprise D1 (2026-10-14, VAL statique du correctif de D1)
Seul le tableau des défauts (état de D1) et la liste des fichiers de travail ont été touchés dans les sections ci-dessus ; tout le reste est inchangé. Les numéros de ligne des sections 2 à 5 décrivent l'état `CAM-B2p-I2` (`.cpp` `73CC3BD6…`, 1969 lignes) ; après la reprise, le code placé après la l. 1889 est décalé de **+7**.
| # | Contrôle | Commande / lecture | Résultat brut | Verdict |
|---|---|---|---|---|
| R1 | MD5, taille, fins de ligne | `pwsh -NoProfile -File scratchpad\v3.ps1` | `.cpp` `size=86153 CR=0 LF=1976 first3=2f2f20 last=0a MD5=0F383EF777DED71608347E17D4A0861F` ; `.h` `size=9813 CR=0 LF=179 MD5=6EDBBCCF61759B8C6EE4890DF9BC5FE7` | **PASS** : égalent l'attendu (MD5, 1976 lignes, `.h` inchangé), LF pur, sans BOM |
| R2 | Instantané `CAM-B2p-I2` | même script, `manifest.csv` et octets | `.cpp` `85842 / CR=0 / LF=1969 / 73CC3BD667059A903E44405C5E793DE3`, `.h` `6EDBBCCF…` : identiques à mon état final validé | **PASS** |
| R3 | Diff I2 vers final | `git diff --no-index -U0` et `--numstat` | `.cpp` **+7 / −0** (`numstat` : `7 0`) ; `.h` : diff vide ; un seul hunk `@@ -1889,0 +1890,7 @@` dans `AttachHelpIcon` ; 1969 + 7 = 1976 | **PASS** |
| R4 | Contenu du hunk (lu en entier, `-U4`) | `.cpp` l. 1890-1896 | juste après `UPanelSlot* IconSlot = SearchParent->AddChild(IconButton);` (l. 1889) : `if (!IconSlot)` puis `UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : icone non inseree, %s (%s) refuse un enfant de plus"), *SearchParent->GetName(), *SearchParent->GetClass()->GetName());` puis `return nullptr;` | **PASS** : texte conforme à la demande (parent et classe) |
| R5 | Aucun usage de `IconSlot` sans garde | Grep `IconSlot` | 3 occurrences : l. 1889 (définition), l. 1890 (garde), l. 1897 (`Cast<UHorizontalBoxSlot>(IconSlot)`) et l. 1902 (`Cast<UCanvasPanelSlot>(IconSlot)`) : les deux `Cast` suivent le retour de la l. 1895 ; la ligne « icone inseree » (l. 1913) et `return IconButton` (l. 1916) aussi | **PASS** : chemin nul = journal « non inseree » puis `nullptr`, rien d'autre |
| R6 | Format `UE_LOG` | lecture l. 1893-1894 | 2 `%s`, 2 arguments (`FString` déréférencées) | **PASS-STATIC** |
| R7 | Effet de `return nullptr` en amont | lecture de `TickControlsHelp` l. 1798-1810 | `HelpCursor` est déjà affecté (une tentative par instance, pas de nouvel essai) ; `HelpButton` reçoit `nullptr` donc `HelpButton.Get()` est nul et le bloc de visibilité et d'info-bulle est sauté ; `IconButton`, sans référence ni parent, est ramassé par le GC (son lien `OnClicked` pointe vers le sous-système, sans effet) | **PASS-STATIC** (ramassage : estimé) |
| R8 | Homonymes nouveaux | lecture du hunk | aucun nouveau local, paramètre, membre ni constante (le hunk n'emploie que `IconSlot`, `SearchParent`, `LogLifePlay`, existants) : pas de C4456, C4458 ni C4459 | **PASS-STATIC** |
| R9 | Fichiers touchés | `Get-ChildItem` | `.cpp` 14:58:56 ; `.h` 14:43:12 (inchangé) ; instantané I2 14:58:51 | **PASS** |
**D1 : levé** (PASS-STATIC). Le cas « `AddChild` rend nul » écrit la ligne « icone non inseree, <parent> (<classe>) refuse un enfant de plus » et rend `nullptr` avant tout usage du slot et avant la ligne « icone inseree » ; la consigne 2 de Big Boss est désormais tenue pour ce cas. L'essai CAM-86 ne peut plus passer à tort sur la seule ligne « icone inseree ».
**Verdict de la reprise : PASS-STATIC**, aucun défaut nouveau, aucun bloquant. Le verdict global de la § 5 reste **PASS-STATIC** (compilation à BUILD, comportement à V7). **D2 (Maj + molette) et D3** restent ouverts, à Big Boss ; ils ne sont pas tranchés ici. Non mesuré en plus : compilation du nouveau `.cpp` ; passage réel par la branche nulle (nécessite un parent à enfant unique, non lu faute de MCP).
Correction de comptage : dans R5, les occurrences de `IconSlot` sont **4** (l. 1889, 1890, 1897, 1902), et non 3.
Tours de la reprise : 5 (écriture du script, exécution, 2 lectures et recherche, écriture du livrable) ; total du livrable : 42 (dépassement du budget de 40 de la fiche d'origine, la reprise étant une demande ultérieure).