LifePlay-Revival/Docs/Migration/etat/CAM/CAM-B2p-I-VAL.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

209 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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