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&, const TVector2&)` (:67), `Min`/`Max` publics (:36-39), `GetCenter() const` (:272) ; `explicit TVector2(const TVector&)` (Vector2D.h:132) : l'appel explicite `FVector2D(Out.FloorBox.Min)` convient ; `TBox::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&)` (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&, 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&, 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` (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()) { 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()` sur `UGameInstance*` (modèle de `GetSubsystem()` 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).