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

109 lines
26 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-F-VAL — Niveau N1 — Modèle prévu sonnet | utilisé sonnet 5.5 (claude-sonnet-5-5) — Agent lp-validator — Skills sk-protocol, sk-recount, debug-verification, lp-validate
# CAM-B2p-F-VAL : intérieur réel de la pièce, pose libre, focus clavier, VAL statique (2026-10-14, reprise G3 incluse)
VAL statique seulement : aucune compilation, aucun éditeur, aucun commandlet, aucun MCP, aucun jeu. Copie
`D:\Game\LifePlay-master 5.8`, branche `ue5.8` (relevée par `git branch --show-current`). Moteur lu : `D:\Game\Launcher\Engine\UE_5.8\Engine\Source`.
Les deux fichiers sont **non suivis** par git (`git status --short` : `??`) ; la base de comparaison est l'instantané
`D:\Game\LifePlay_Backup_5.5\CAM-B2p-F\` (manifeste : 2 fichiers seulement). Les sections 1 à 6 valident l'état
`.cpp` `8C6A3D71…` / `.h` `3DF4B511…` ; l'ajout G3 fait ensuite est validé dans la section « Reprise G3 » (état final
`.cpp` `03D85D28…`, `.h` `4AE65994…`). Le verdict final est celui de la section « Reprise G3 ».
## 1. Fiches amont relues
- `Docs/Migration/etat/CAM/CAM-B2p-F-IMPL.md` (lu en entier).
- `Docs/Migration/etat/CAM/CAM-COMMUN9-DIAG.md` § 6 (6.1 à 6.6), lignes 103-224 lues en entier.
- Comparaison de règle : `Source/LifePlay/LPShowCoupleSubsystem.cpp` l. 38-62 (`IsBedMesh`) et l. 770-778 (`IsIgnoredShell`), lecture seule.
- Pour la reprise G3 : `Source/LifePlay/LPShowCoupleSubsystem.h` (en entier) et `.cpp` l. 805-824 (`NotifyCoupleTeleported`), lecture seule ; `Plugins/LPAnimCore/Source/LPAnimCore/LPCoupleSim.h`.
- Pas d'autre IMPL dans ce lot (B2p-A à B2p-E déjà validés, hors périmètre).
## 2. Points à confirmer des IMPL (IMPL § 3)
| Identifiant | Point | Résultat |
|---|---|---|
| P1 | Ligne F2 : source « murs Wall* », bornes X et Y dans celles de `Box`, plafond trouvé ou non | reporté (COMMUN-11, journal de jeu). Statique : le format de la ligne est correct (16 spécificateurs, 16 arguments, § 3 C3e) |
| P2 | G1 : lits non refusés, meubles et murs refusés | reporté (COMMUN-11, CAM-73). Statique : la règle des lits est identique à `IsBedMesh` (§ 3 C5e) |
| P3 | G1 : couple coincé se dégage vers une place libre (complet avec G3) | reporté (CAM-74) ; la partie CAM est PASS-STATIC (§ 3 C5e) ; l'appel G3 est maintenant livré et validé statiquement (Reprise G3) ; l'effet réel reste à voir en jeu |
| P4 | H1 et H2 : ZQSD remarche après un clic dans la vue ; un clic dans le champ y remet le focus | reporté (CAM-75). Statique : logique conforme (§ 3 C5g) ; l'effet de `SetAllUserFocusToGameViewport` et le fait que le `PlayerInput` ne voie pas un clic pris par l'interface ne se prouvent qu'en jeu |
| P5 | Compilation (`/we4458`, etc.) | BLOCKED-pas de build (VAL statique demandée) ; relecture et contrôle d'ombrage PASS-STATIC (§ 3 C3, C4 ; Reprise G3). À rejouer par `Build.ps1` + `Build.ps1 -FromLog` à la prochaine compilation |
| P6 | Essais CAM-69 à CAM-75 | reporté (COMMUN-11) |
## 3. Critères rejoués
| # | Contrôle | Commande ou lecture | Résultat brut | Verdict |
|---|---|---|---|---|
| C1 | MD5, lignes, CR | `md5sum`, `wc -l`, `tr -cd '\r' \| wc -c` sur le .cpp et le .h | `.cpp` `8c6a3d719fbc60900cc35bad46bf6d51`, 1426 l., 0 CR ; `.h` `3df4b51155a42bcc0f99fcc26cfd4f6e`, 130 l., 0 CR. Instantané : `.cpp` `fff0fbb45d493b931b08780bf09547c8` (1163 l., 0 CR), `.h` `0edc1f76b7eec0d135ea046da2269be0` (121 l.) | PASS : identique à l'IMPL (casse mise à part) |
| C2 | Diff complet instantané → actuel | `git diff --no-index --numstat` puis `-U3` du .cpp, `-U2` du .h, lus en entier | `.cpp` 288 ajouts, 25 retraits (1163 + 288 − 25 = 1426) ; `.h` 9 ajouts, 0 retrait (121 + 9 = 130). Hunks : 2 includes, constantes (CoupleHeadroom, bloc F/G), `FRoomInterior` + `ComputeRoomInterior` + `CoupleSpotFree` + `FocusedEditable`, `TickCoupleBounds` (3 hunks), `TickSexCamera` (1), `TickCoupleMouse` (3 : `LastSpotBlocker.Reset`, `bGrabJustEnded`, F3 + G1). `.h` : 5 membres + 4 commentaires | PASS : uniquement le périmètre de l'IMPL. Aucun ajout non demandé (voir D4 : un retour anticipé mort, sans effet) |
| C3a | Types moteur | Lecture de `Box2D.h`, `Vector2D.h`, `Box.h`, `OverlapResult.h`, `World.h`, `CollisionQueryParams.h`, `CollisionShape.h` (PhysicsCore), `UnrealType.h`, `Actor.h`, `SceneComponent.h`, `SlateApplication.h`, `SEditableText.h`, `SharedPointer.h` | `TBox2(EForceInit)` explicite (Box2D.h:56), `TBox2(const TVector2<T>&, const TVector2<T>&)` (:67), `Min`/`Max` publics (:36-39), `GetCenter() const` (:272) ; `explicit TVector2(const TVector<T>&)` (Vector2D.h:132) : l'appel explicite `FVector2D(Out.FloorBox.Min)` convient ; `TBox<T>::GetSize()` (Box.h:419) et `GetCenter()` (:374) const ; `FOverlapResult::bBlockingHit` (OverlapResult.h:49), `GetComponent() const` (:39) ; `UWorld::OverlapMultiByChannel(...) const` (World.h:2418) ; `AddIgnoredActors(const TArray<AActor*>&)` (CollisionQueryParams.h:257), `AddIgnoredComponent(const UPrimitiveComponent*)` (:267) ; `MakeBox(const FVector&)` (CollisionShape.h:284) ; `GetKeyboardFocusedWidget() const` (SlateApplication.h:1653), `SetAllUserFocusToGameViewport(EFocusCause = SetDirectly)` (:649) ; `SEditableText::GetText() const` public (SEditableText.h:232) ; `GetComponentScale() const` (SceneComponent.h:1084) ; `GetRootComponent() const` (Actor.h:2481) ; `GetComponents(TArray<T*, A>&, bool) const` (Actor.h:4024) ; `TSharedPtr(FNullTag* = nullptr)` non explicite (SharedPointer.h:706) ; `Engine/OverlapResult.h` existe (Glob) | PASS-STATIC : toutes les signatures utilisées existent avec la bonne constance |
| C3b | `FScriptArrayHelper` sur `const AActor*` | `UnrealType.h:805-816` : surcharges `ContainerPtrToValuePtr(UObject const*)` et `(void const*)` ; `FScriptArrayHelper(const FArrayProperty*, const void*)` (:4285). Comparaison : `ReadActors` (cpp l. 215) fait la même chose avec `const UObject*` | `const AActor*` → `UObject const*` (conversion dérivé vers base) est préférée à `void const*` : pas d'ambiguïté. Même motif que `LPModDailies.cpp:137` (`Room` const aussi) | PASS-STATIC |
| C3c | Types float/double, constance | Relecture ligne à ligne de `ComputeRoomInterior` (l. 337-440), `CoupleSpotFree` (l. 445-480), `FocusedEditable` (l. 483-491) | `FMath::Min/Max` toujours à deux arguments de même type (double/double, int32/int32) ; `FRotator(0.0, Yaw, 0.0)` en double ; `Margin` double, constantes float élargies sans perte ; `ComputeRoomInterior(const AActor*, FRoomInterior&)`, `CoupleSpotFree(const UWorld*, const AActor*, const UPrimitiveComponent*, const TArray<AActor*>&, const FVector&, double, FString&)` : les appels (l. 752, 1108, 1130) passent `AActor*`, `const AActor*`, `const UWorld*`, `const UPrimitiveComponent*` : conformes. `FocusedEditable` : `cond ? Focused : nullptr` converti en `TSharedPtr<SWidget>` (même motif que `LPDirectControlSubsystem.cpp:951`) | PASS-STATIC |
| C3d | Lambdas | l. 354-390 (`UseStructure`, `[&]`) | Captures par référence de `Out`, `Floor`, `Walled`, `Center` : toutes déclarées avant la lambda, durée de vie couvrant l'appel (la lambda ne sort pas de la fonction). Type de retour explicite `-> bool`, tous les chemins retournent | PASS-STATIC |
| C3e | Format `UE_LOG` | `Grep -o '%[-+.0-9]*[dfsi]'` sur le fichier, lignes 764, 801, 848, 1142 | Ligne 764 (F2) : 16 spécificateurs (7 `%.0f`, `%s`, `%d`, `%d`, 4 `%.0f`, 2 `%.2f`) pour 16 arguments (Area 4, FloorTop, Ceiling, Margin, `*Source`, Walls, `bCeilingFound?1:0`, FloorBox 4, Scale 2) : **16 et non 15**, accord complet ; types : doubles pour `%f`, `int32` pour `%d`, `TCHAR*` pour `%s`. Ligne 801 : 7 pour 7. Ligne 848 : 2 `%s` (`Why` est un `const TCHAR*`, `*Editable->GetTypeAsString()`) pour 2. Ligne 1142 : 1 `%s` pour `*Blocker` | PASS-STATIC |
| C3f | Qualification des aides | `Grep` des appels | Tous les appels aux aides passent par `LPCameraModeLocal::` dans les fonctions membres (l. 752, 1108, 1130, 830, 850) ; dans l'espace, `ComputeRoomInterior` appelle `ReadObject` non qualifié (même espace) | PASS-STATIC |
| C4 | Ombrage de tout le fichier | Membres du .h à jour (liste de 41 noms dont les 5 nouveaux) contre les locaux, paramètres et captures ; `Grep` des noms nouveaux (`Interior`, `Area`, `Margin`, `Editable`, `Why`, `Blocker`, `bClick`, `bGiveBack`, `bSexOn`, `bGrabEnded`, `FocusWorld`, `FocusPC`, `LeadMesh`, `LeadCharacter`, `RootOffset`) ; relecture des scopes | Aucun nom de membre (`bGrabJustEnded`, `bBoundsLogged`, `LastSpotBlocker`, `bSceneFocusChecked`, `bFocusClickWasDown`, `Root`, `Text`, `Viewport`, …) repris par un local ou un paramètre. `TickCoupleBounds` : `Floor` (l. 736) précède `Interior`, pas de doublon ; `Person` en trois boucles sœurs (préexistant). `TickCoupleMouse` : `Interior`, `Area`, `Margin`, `Blocker`, `Delta`, `Person` dans des blocs imbriqués sans homonyme englobant ; les deux `SurfaceZ` (l. 1071, 1119) et les deux `Aim` (l. 1074, 1102) sont dans des blocs frères. `TickSexCamera` : `bSexOn` unique, `Editable`/`Why`/`bClick`/`bGiveBack`/`FocusWorld`/`FocusPC` dans le bloc `else` seulement, `World`/`PC` du reste de la fonction déclarés **après** ce bloc et sous d'autres noms. `ComputeRoomInterior` : locaux `Floor, Center, RootComponent, Walled, UseStructure, Components, Prop, Inner, Array, Index, Location, HalfX, HalfY` ; lambda : paramètre `Comp`, locaux `Bounds, Size` ; la boucle `for (… Comp : Components)` vient **après** la lambda, donc pas de masquage ; aucun homonyme paramètre/local (`Room`, `Out`). `CoupleSpotFree` : paramètres `World, Room, Floor, Couple, RootSpot, Yaw, OutBlocker` ; locaux `Query, Overlaps, Center, Overlap, Comp, MeshComp, Mesh, MeshName` : aucun doublon. `FocusedEditable` : `Focused`. **C4459** (erreur aussi : UBT `CppCompileWarnings.cs:89` donne `/we4456 /we4458 /we4459` pour `ShadowVariableErrors`) : aucun local nouveau ne reprend un nom de portée d'espace de `LPCameraModeLocal` (liste des constantes et fonctions lue l. 34-511) ; `Grep` des déclarations de portée espace dans `Source/**` : seul `MeshName` existe ailleurs (`LPDirectControlSubsystem.cpp:780`, fonction), déclaré dans un fichier qui suit `LPCameraModeSubsystem.cpp` dans l'ordre alphabétique d'une unité unifiée, donc non visible ici | PASS-STATIC : 0 masquage trouvé (C4456, C4457, C4458, C4459). Estimé pour C4459 inter-fichiers (dépend de la composition des unités unifiées) |
| C5a | F1 : murs, replis, incohérence, plafond | l. 337-440 | Murs : composants de Room dont le nom commence par `Wall` (l. 397, insensible à la casse), critères haut > 100 cm et petit côté ≤ quart du grand, borne resserrée selon le côté par rapport au centre du sol. Repli 1 `AllComponents[0..5]` (l. 403, `RoomStructureCount` = 6) seulement si 0 mur ; repli 2 demi-côté d'échelle × 50 cm autour de `Room` (l. 421-433), dans la boîte du sol. Incohérence (largeur ou profondeur ≤ 1 cm) : retour à la boîte du sol, source `sol (murs incoherents)`. Plafond : composant mince en Z (< 50 cm) et à plus de 150 cm au-dessus du sol, borne = `Min.Z` le plus bas, défaut sol + 350 | PASS-STATIC (voir D1, D2) |
| C5b | E1 : intérieur + marge 80, pièce étroite, plafond − 120 | l. 758-790 | Marge `CoupleWallMargin` = 80 ; si largeur ≤ 2 × marge, la correction va au centre de l'axe ; borne haute `Ceiling − CoupleHeadroom` (120). Z bas inchangé. Ligne de correction : `plafond .. (racine au plus ..)` | PASS-STATIC |
| C5c | F2 : une ligne par scène | l. 704, 760-768 | `bBoundsLogged` posé avant le log, remis à faux dans la branche « hors sexe » (l. 704). Placé après le retour « pièce cachée » : la ligne sort à la première image où la borne s'applique | PASS-STATIC (voir D6) |
| C5d | F3 : même intérieur dans le glisser | l. 1105-1117 | Même fonction, même marge ; condition de largeur identique. Une pièce étroite n'est pas recentrée par le glisser (E1 le fait, image suivante) : comportement préexistant | PASS-STATIC |
| C5e | G1 : refus, lits, ligne, place encombrée au départ, nullité | l. 445-480, 1124-1143 | Recouvrement bloquant (`bBlockingHit`) d'un composant dont le propriétaire est Room (`Comp->GetOwner() != Room` → ignoré), sol (`Floor`) et couple ignorés. Règle des lits : `bed` (IgnoreCase), `fnt_ext011` (Contains), `fnt_new045` et `fnt_new235` (Equals) : **identique caractère pour caractère** à `IsBedMesh` (`LPShowCoupleSubsystem.cpp:56-59`) ; maillage nul : non lit dans les deux (STE : `false` ; ici nom vide donc bloquant). Boîte demi-étendue 78 × 46 × 50, centre à racine + 58 (bas à +8 cm), lacet du meneur. Ligne de refus : écrite seulement si `Blocker != LastSpotBlocker`. La place de départ n'est jamais testée (seule la cible l'est) : une prise qui part d'une place encombrée peut aller vers toute place libre. Nullité : `Couple[0]` (l. 1126-1131) : `bScene` exige `Couple.Num() > 0` (l. 962) et la fonction retourne avant (l. 963-968) ; déjà utilisé l. 1084. `RoomActor` nul : `CoupleSpotFree` retourne vrai (l. 447) ; `ComputeRoomInterior(nullptr, …)` retourne faux (l. 340). `Blocker` est une `FString` (pas de pointeur) | PASS-STATIC (voir D3, D7) |
| C5f | F4 : `bGrabJustEnded` | l. 688-689, 698-699, 723, 1093 | Appelant : `TickSexCamera` (qui appelle `TickCoupleMouse`) puis `TickCoupleBounds` dans la même image. Pose au relâcher (l. 1093), lecture puis remise à faux avant toute sortie anticipée (l. 698-699), donc jamais de drapeau périmé ; l'image du relâcher n'est pas comptée par le témoin (l. 723). Le déplacement visé est celui que le résolveur de STE applique après les acteurs, vu à l'image où le mode passe à 0 | PASS-STATIC |
| C5g | H1 et H2 | l. 815-852 | Hors sexe : remise à faux des deux drapeaux, aucun appel Slate. H1 : `Editable` valide, bouton gauche ou droit enfoncé, front (`!bFocusClickWasDown`) → focus rendu au viewport, `bMenuOnScreen` recalculé. H2 : seulement à l'image où `bSceneFocusChecked` est faux, seulement si le type est exactement `SEditableText` et `GetText().IsEmpty()`. `bSceneFocusChecked = true` posé à chaque première image, même sans champ (une fois par scène). `Editable` déréférencé dans le log seulement si `bGiveBack`, vrai seulement si `Editable.IsValid()` dans les deux branches. `GI` non nul dans le bloc `else` (`bSexOn` l'implique) | PASS-STATIC |
| C5h | Déréférences nulles | l. 337-491, 698-806, 815-852, 1105-1143 | `Room` nul (ComputeRoomInterior, CoupleSpotFree) géré ; `RootComponent` nul géré (échelle 1) ; `Floor` testé par `IsValid` ; `Comp` testé dans la boucle et la lambda ; `LeadCharacter`/`LeadMesh` nuls gérés (`RootOffset` = 0) ; `FocusPC->PlayerInput` testé | PASS-STATIC |
| C6 | Cohérence DIAG / IMPL | DIAG § 6.2 à 6.6 contre IMPL § 1 et § 2 | Écarts déclarés ou visibles, tous acceptables : (i) format de la ligne F2 (DIAG : `murs trouves n/6` ; réalisé : `source …, murs n`, marge ajoutée) : le DIAG § 6.4 impose les murs par nom, donc `n/6` n'a plus de sens ; (ii) marge 80 au lieu de 30 : DIAG 6.4 G2 ; (iii) `OverlapMultiByChannel` à la place de `OverlapAnyTestByChannel` : nécessaire pour nommer le composant refusé ; (iv) G3 non appelé à l'époque, nom et signature proposés à STE : déclaré IMPL § 2, conforme à DIAG 6.4 (appel livré ensuite : Reprise G3) ; (v) `CoupleFloorMargin` laissé inutilisé (IMPL § 2) ; (vi) plafond − 120 : sol + 230 si plafond estimé (IMPL § 2). DIAG 6.2 voulait le repli d'échelle « aussi » comme borne ; réalisé en repli seulement si 0 mur (voir D1) | PASS-STATIC |
| C7 | Fichiers modifiés hors périmètre | `git status --short` des deux fichiers ; manifeste de l'instantané (2 lignes) | Seuls `.cpp` et `.h` figurent au manifeste ; `git diff --no-index --stat` instantané → arbre : les 25 retraits sont ceux du .cpp | PASS |
## 4. Diff lu en entier
- Fichiers : `Source/LifePlay/LPCameraModeSubsystem.cpp` (+288 −25, 11 hunks) et `Source/LifePlay/LPCameraModeSubsystem.h` (+9 −0, 1 hunk) pour l'état validé en première passe ; l'ajout G3 est lu dans « Reprise G3 ».
- Le compte recalculé (1163 + 288 − 25 = 1426 ; 121 + 9 = 130) égale celui de l'IMPL.
- Aucune modification par script détectée (pas de fins de ligne mixtes : 0 CR dans les deux fichiers, LF comme l'instantané).
- `UE_VERSION_OLDER_THAN` : non utilisé dans ces fichiers, sans objet.
## 5. Verdict
**PASS-STATIC.** Aucun défaut bloquant. Le critère « compile sous `/we4456 /we4458 /we4459` » est établi par lecture seule (estimé) :
il reste à rejouer par une compilation (`Build.ps1` puis `Build.ps1 -FromLog`) et `LPTestStringCompat` n'a pas été rejoué (aucun build lancé).
Verdict final après la reprise G3 : voir la section « Reprise G3 » (inchangé : PASS-STATIC).
## 6. Points à confirmer par l'utilisateur
- [ ] COMMUN-11, ligne F2 : source « murs Wall* », murs n ≥ 1 (idéalement 4), bornes X et Y à l'intérieur de `Box`, plafond trouvé ou non (D1, D2).
- [ ] CAM-69 à CAM-72 : le couple s'arrête à 80 cm des murs (glisser, flèches), plafond − 120 cm (Maj + molette).
- [ ] CAM-73, CAM-74 : refus d'un meuble ou d'un mur sans refus d'un lit ; couple coincé dégagé (avec G3 : appel livré, effet à voir en jeu, voir R2 de la reprise).
- [ ] CAM-75 : focus clavier rendu par un clic dans la vue, repris par un clic dans le champ ; ZQSD remarche (D5).
- [ ] Scène d'extérieur (pièce cachée) : un glisser n'est pas refusé à tort par G1 (D7).
- [ ] Compilation et journal d'avertissements au prochain build.
Tours : 24 / 40
## Reprise G3 (appel du contrat STE, vérifié après la première VAL)
Même méthode, toujours statique. Instantané de l'état validé : `D:\Game\LifePlay_Backup_5.5\CAM-B2p-F-G3\` (manifeste de 2 fichiers).
| # | Contrôle | Commande ou lecture | Résultat brut | Verdict |
|---|---|---|---|---|
| G-1 | MD5, lignes, CR | `md5sum`, `wc -l`, `tr -cd '\r' \| wc -c` | `.cpp` `03d85d282a6306e6d2494d7dc2c253fc`, 1437 l., 0 CR ; `.h` `4ae6599447750371cff7e0aac74eb93e`, 132 l., 0 CR. Annonce : `03D85D28…` 1437, `4AE65994…` 132. Instantané G3 : `.cpp` `8c6a3d71…` (1426 l.), `.h` `3df4b511…` (130 l.) = l'état validé en première passe | PASS : conforme |
| G-2 | Diff contre l'instantané G3 | `git diff --no-index -U3` (.cpp) et `-U2` (.h), lus en entier | `.cpp` : 3 hunks, +11 lignes (1426 + 11 = 1437) : `#include "LPShowCoupleSubsystem.h"` (l. 20) ; `GrabLeadStart = Couple[0]->GetActorLocation();` à la prise (l. 1072) ; commentaire de 2 lignes + bloc `if (FVector::Dist(Couple[0]->GetActorLocation(), GrabLeadStart) > 1.0) { if (ULPShowCoupleSubsystem* ShowCouple = GI->GetSubsystem<ULPShowCoupleSubsystem>()) { ShowCouple->NotifyCoupleTeleported(); } }` avant `bGrabJustEnded = true;` (l. 1095-1104). `.h` : 1 hunk, +2 lignes (130 + 2 = 132) : commentaire et `FVector GrabLeadStart = FVector::ZeroVector;` (l. 123-124). Aucun retrait | PASS : exactement les changements attendus, rien d'autre |
| G-3 | Contrat STE | `Read` de `LPShowCoupleSubsystem.h` en entier ; `Grep NotifyCoupleTeleported` dans `Source/LifePlay` ; MD5 | `void NotifyCoupleTeleported();` déclaré en section `public:` (h l. 24-30), défini `.cpp` l. 807-824 : retourne sans effet si pas engagé, sans meneur, sans partenaire ou sans monde ; sinon `LastSafe = position du meneur` et `LastBlocker.Reset()`. MD5 en lecture seule : `.h` `44c721ee9e82bdfdf7571e493af70e39`, `.cpp` `3cdd572d78b691eef0a3f02b1b37a486` (attendus `44C721EE…`, `3CDD572D…`). Les seuls appels dans `Source` : celui de CAM (l. 1101) | PASS-STATIC |
| G-4 | Un appel au plus par prise, rien par image | Lecture l. 1063-1107 et 1082-1107 | L'initialisation de `GrabLeadStart` est dans le bloc `CoupleGrabMode == 0` qui crée la prise (une fois par prise). L'appel est dans le bloc `if (!bHeld)` qui termine la prise : il remet `CoupleGrabMode = 0` et retourne `false` (l. 1105-1107), donc la prise suivante repart du bloc de création. Aucun appel ni déclaration dans la boucle du glisser (l. 1110-1160). L'appel est conditionnel : le meneur doit s'être déplacé de plus d'1 cm (`FVector::Dist` renvoie un double) depuis le début de la prise ; un simple clic ou une rotation sans déplacement du meneur n'appelle rien | PASS-STATIC |
| G-5 | Nullité et portée | Lecture | `Couple[0]` : `bScene` exige `Couple.Num() > 0` et la fonction retourne avant (l. 963-968) ; `Couple[0]->GetActorRotation()` est déjà lu quelques lignes plus haut dans le même bloc (l. 1085). `GrabLeadStart` est posé à chaque création de prise avant que le mode passe à non nul puis qu'il serve : jamais lu non initialisé. `GI` : paramètre de `TickCoupleMouse`, déjà déréférencé au début (`GI->GetWorld()`, l. 961) ; l'appelant (`TickSexCamera`) n'y arrive qu'avec `bSexOn` (donc `GI` non nul). `GetSubsystem` rend un pointeur testé par le `if` d'initialisation | PASS-STATIC |
| G-6 | Compilation, types | Lecture | `ULPShowCoupleSubsystem` complet par l'include ; méthode publique non const appelée sur `ULPShowCoupleSubsystem*` ; `GI->GetSubsystem<T>()` sur `UGameInstance*` (modèle de `GetSubsystem<ULPKeyboardSubsystem>()` en l. 287). L'include `LPShowCoupleSubsystem.h` est du même module `LifePlay` que `LPKeyboardSubsystem.h` ; il tire `LPCoupleSim.h` du plugin `LPAnimCore`, déjà inclus par `LPShowCoupleSubsystem.cpp` dans ce module (même chemin d'inclusion). `.generated.h` à la charge d'UHT (aucune nouvelle UCLASS dans ce lot) | PASS-STATIC |
| G-7 | Ombrage C4456 à C4459 | `Grep \bShowCouple\b\|\bGrabLeadStart\b` dans `Source/**` ; liste des déclarations de `LPShowCoupleSubsystem.h` ; `Grep` des déclarations de portée globale de `LPCoupleSim.h` | `GrabLeadStart` : une seule déclaration (membre, `.h` l. 124) ; aucun local ni paramètre homonyme (C4458 écarté). `ShowCouple` : une seule occurrence en identifiant, local à l'`if` d'initialisation (l. 1099) ; hors `Source`, `lp.ShowCouple` n'est qu'une chaîne (`LPShowCoupleSubsystem.cpp:29`). `LPShowCoupleSubsystem.h` apporte la classe `ULPShowCoupleSubsystem` (membres privés `Lead`, `Partner`, `Box`, `LastSafe`, … propres à cette classe, non visibles de `ULPCameraModeSubsystem`), les déclarations avancées `ACharacter`, `UAnimSequence`, `UBoxComponent`, `UPrimitiveComponent`. `LPCoupleSim.h` (`Plugins/LPAnimCore/Source/LPAnimCore/`) ne déclare à portée globale que des déclarations avancées de classes et `namespace LPCoupleSim` : aucun nom de variable ni de fonction globale, et le .cpp de CAM n'a pas de `using namespace`. Les noms de portée d'espace de `LPCameraModeLocal` (relevés en C4) ne sont pas touchés. Pas de macro ajoutée | PASS-STATIC : 0 masquage (C4456, C4457, C4458, C4459). Estimé pour la visibilité en unité unifiée : `LPShowCoupleSubsystem.cpp` suit `LPCameraModeSubsystem.cpp` dans l'ordre alphabétique |
Défauts de la reprise : aucun bloquant.
- **R1 (info)** : le commentaire du .cpp parle d'une rotation « autour d'un pivot hors du meneur » : la condition `Dist > 1.0` couvre bien ce cas (le meneur bouge) sans appel inutile sinon.
- **R2 (l. 1095-1104, incertain)** : l'appel a lieu au relâcher, pas à chaque pas validé par G1 comme le prévoyait l'IMPL § 2 (« après un déplacement validé »). Pendant le glisser, le résolveur de STE balaie encore depuis l'ancien `LastSafe` à chaque image ; un couple coincé dont le premier pas est refusé par ce balayage ne serait donc dégagé qu'à partir du moment où le résolveur laisse passer le meneur. Conforme à ce que la reprise demande (un appel au plus par prise) ; l'effet se juge au CAM-74 en jeu.
- **R3 (info)** : au relâcher, `Couple[0]->GetActorLocation()` est lu après les corrections du résolveur de l'image précédente : `LastSafe` reprend la place réelle du meneur, ce que `NotifyCoupleTeleported` documente (« la place du meneur »).
**Verdict final : PASS-STATIC.** Les 6 contrôles de la reprise passent par lecture ; rien n'a été compilé ni joué. Restent à faire : compilation (`Build.ps1` + `-FromLog`, 0 erreur sous `/we4456 /we4458 /we4459`), COMMUN-11 (F2, CAM-69 à CAM-75, dont CAM-74 pour l'effet de G3 et R2).
Tours : 24 / 40 (première passe 18, reprise 6)
## Constats
Aucun défaut bloquant. Défauts non bloquants (avec ligne du .cpp ; numéros de la première passe, l. 1126-1143 etc. décalées de +11 après l. 1072 dans l'état final) :
- **D1 (l. 403, 421)** : les replis ne s'appliquent qu'avec 0 mur nommé `Wall*`. Si seulement une partie des murs porte ce nom, les côtés non reconnus restent à la boîte du sol (trop large, cause de COMMUN-10), sans plafonnement par l'échelle. La ligne F2 (`murs n`) le montre ; le DIAG 6.2 prévoyait la borne d'échelle « au cas où les murs ne seraient pas reconnus ». Estimé : à lire en COMMUN-11.
- **D2 (l. 397, 403)** : le plafond n'est cherché que parmi les composants `Wall*`, ou parmi `AllComponents[0..5]` si aucun mur n'est nommé. Si le plafond a un autre nom et que des murs sont trouvés, la F2 affichera `plafond trouve 0` et la borne sera sol + 230. Conservateur, sans danger.
- **D3 (l. 1139-1141 avant G3 ; 1150-1152 après)** : `LastSpotBlocker` n'est remis à vide qu'à la prise, pas après une pose réussie : un second refus par le même composant, après une pose libre, n'écrit pas de ligne. Conforme à « une ligne par changement de composant », mais une ligne manquera dans le journal.
- **D4 (l. 752-757)** : retour anticipé si `ComputeRoomInterior` rend faux : absent de l'IMPL, et chemin mort (le sol a déjà été validé l. 736-747). Sans effet.
- **D5 (l. 828-851)** : H1 suppose que le `PlayerInput` ne voit pas un clic absorbé par l'interface. Non prouvable statiquement (incertain) : CAM-75.
- **D6 (l. 760)** : la ligne F2 sort après le retour « pièce cachée » ; en scène d'extérieur, pas de ligne F2 (cohérent avec « pas de borne »).
- **D7 (l. 1130 avant G3 ; 1141 après)** : avec une pièce cachée, `bFloorKnown` est faux : le sol n'est pas passé à `CoupleSpotFree` et la Room cachée reste testée ; si ses composants chevauchent la place visée en extérieur, la pose sera refusée. Incertain : essai d'un glisser en extérieur.
- **D8 (l. 128)** : `CoupleFloorMargin` inutilisé. MSVC n'avertit pas sur une constante de portée d'espace ; un compilateur Clang le ferait (`-Wunused-const-variable`). À retirer au nettoyage.
- **D9 (l. 156)** : la boîte de test (demi-étendue 78 × 46) tournée de 45° dépasse la marge de 80 cm aux coins (≈ 90 cm) : près d'un mur, G1 peut refuser une place que le clamp autorise. Conservateur, aucun passage de mur.
Compte rendu : l'écart entre « 15 spécificateurs » de la consigne et 16 relevés est une erreur de la consigne (16 spécificateurs, 16 arguments, accord vérifié).
Fichiers à commiter : A `Docs/Migration/etat/CAM/CAM-B2p-F-VAL.md` (seul fichier que cette VAL a écrit, réécrit en entier à la reprise) ; les `.cpp` et `.h` du sous-système restent à commiter par le gardien (non suivis).