Fiche CAM-B2p-D-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-D-VAL : couple à la souris pendant le sexe, VAL statique (2026-10-14, reprise D-1 incluse) Hors gabarit : section « 7. Reprise » ajoutée en fin de livrable (revérification de la seule modification faite après la VAL initiale). Périmètre : VAL **statique** (lecture seule). Aucune compilation, aucun éditeur, aucun commandlet, aucun MCP, aucun jeu, aucune écriture git. Seul fichier écrit : ce livrable. **Numéros de ligne des sections 2 à 6** : ils se rapportent au `.cpp` de la VAL initiale (MD5 `3DB310DC…`, 1042 lignes). Après la reprise (MD5 `D6063264…`, 1044 lignes), toute ligne à partir de la 660 est décalée de + 2 (§ 7). ## 1. Fiches amont relues | Fiche | Lue | Remarque | |---|---|---| | `Docs/Migration/etat/CAM/CAM-B2p-SPEC.md` § 7 à § 7.11 (l. 161-331) | oui, en entier | § 7.3 (hauteur) et § 7.4 (rotation) remplacés par § 7.11 | | `Docs/Migration/etat/CAM/CAM-B2p-D-IMPL.md` | oui, en entier, y compris le paragraphe « Reprise après la VAL (D-1) » (l. 66-77) | seule IMPL du lot B2p-D ; § 3 = points à confirmer repris en § 2 | | Instantané `D:\Game\LifePlay_Backup_5.5\CAM-B2p-D\` (`manifest.csv`, sous-arbre `Source\LifePlay`) | oui | MD5 du manifeste = MD5 annoncés « avant » | | `Source/LifePlay/LPCameraModeSubsystem.cpp` et `.h` (état actuel) | oui, en entier pour les zones modifiées (l. 1-260, 470-790 du .cpp ; .h entier) | | B2r et B2p-C (validés) ne sont pas rouverts : seul leur inchangé hors points déclarés est contrôlé (§ 3, contrôle 2). ## 2. Points à confirmer des IMPL Source : `CAM-B2p-D-IMPL.md` § 3. | Id | Point (IMPL § 3) | Résultat | Preuve ou motif | |---|---|---|---| | PC-1 | Compilation : signature `GetMousePosition(float&, float&)` | PASS-STATIC | `PlayerController.h:1623` (surcharge `double&` en 1624, la `float&` du code est exacte) ; implémentation `PlayerController.cpp:6166` | | PC-2 | `DeprojectMousePositionToWorld(FVector&, FVector&) const` | PASS-STATIC | `PlayerController.h:731` | | PC-3 | `ProjectWorldLocationToScreen(FVector, FVector2D&, bool) const` | PASS-STATIC | `PlayerController.h:742` ; même espace de coordonnées que la souris (pixels de vue : `GameplayStatics.cpp:3319-3343`, `GameViewportClient.cpp:1094-1110`) | | PC-4 | `FCollisionQueryParams::AddIgnoredActors(TArray)` | PASS-STATIC | `CollisionQueryParams.h:257` : `const TArray&`, correspondance exacte avec `Couple` (const ref) | | PC-5 | `FMath::PointDistToSegment` | PASS-STATIC | `UnrealMathUtility.h:2017` : `(const FVector&, const FVector&, const FVector&)` rend `float` ; `FVector(FVector2D, T)` explicite en `Vector.h:176`, appelé en initialisation directe | | PC-6 | Compilation réelle (COMMUN-8) | BLOCKED-compilation (reporté COMMUN-8) | VAL statique, jeton UE tenu par STE | | PC-7 | En jeu : position du curseur en mode Scene ; clic gauche sur le couple et `SelectUnderCursor` ; menu au clic droit d'un widget de scène ; hauteur sur le lit juste ; borne au sol ; aucune ligne `Accessed None` nouvelle | reporté V7 (COMMUN-8) | non vérifiable sans jeu ; la ligne « curseur sans position » (l. 666-670) répondra à la première question | | PC-8 | Essais CAM-58 à CAM-63 (SPEC § 7.11) | reporté V7 | non rejoués ; chaque essai est relié à son code en § 3, contrôle 4 | | PC-9 | Reprise D-1 (IMPL l. 66-68) : avec Maj tenu, aucune prise | PASS-STATIC | § 7 | ## 3. Critères rejoués (VAL initiale, .cpp `3DB310DC…`) ### Contrôle 1 : empreintes, lignes, fins de ligne Commande : `md5sum`, `wc -l`, `tr -cd '\r' | wc -c` (actuel et instantané), puis `Tools/FileCheck.ps1 -Path ...`. ``` 3db310dc025f50b9bc5b0681be84b15e *LPCameraModeSubsystem.cpp 1042 CR=0 6585d7f67a7be3aea377b8e93cfa14d8 *LPCameraModeSubsystem.h 112 CR=0 instantané : 8e35e07ae08f3f208a24dc4cf2b3338f .cpp 820 CR=0 ; 9e20981f8c768371a0935e2c7ce9e134 .h 84 CR=0 FileCheck : LF 0 1042 0 - 3DB310DC... alertes=0 (.cpp) ; LF 0 112 0 - 6585D7F6... alertes=0 (.h) ; fichiers=2 LF=2 alertes=0 ``` | Élément | IMPL | Recompté | Verdict | |---|---|---|---| | MD5 .cpp après | `3DB310DC025F50B9BC5B0681BE84B15E` | `3DB310DC025F50B9BC5B0681BE84B15E` | confirmé (état initial ; état final : § 7) | | MD5 .h après | `6585D7F67A7BE3AEA377B8E93CFA14D8` | `6585D7F67A7BE3AEA377B8E93CFA14D8` | confirmé | | MD5 .cpp / .h avant | `8E35E07A…` / `9E20981F…` | identiques au `manifest.csv` et au sous-arbre de l'instantané | confirmé | | Lignes | 820 → 1042 ; 84 → 112 | 820 → 1042 ; 84 → 112 | confirmé | | CR / BOM | 0 CR, LF | 0 CR, pas de BOM, aucune alerte FileCheck | confirmé | | Suivi git | « non suivis jusqu'ici » | `git status --short` : `??` pour les deux ; `git ls-files` : vide | confirmé (A) | ### Contrôle 2 : diff complet instantané → actuel, périmètre Commandes : `diff -u` (lu en entier, .h puis .cpp) ; `git diff --no-index --numstat`. ``` 228 6 LPCameraModeSubsystem.cpp (820 + 228 - 6 = 1042) 28 0 LPCameraModeSubsystem.h (84 + 28 = 112) ``` `diff -q` rend 1 (différent), comme attendu. Hunks du .cpp : 7 (constantes ; garde début de `TickSexCamera` ; `PC` non const ; `ReleaseCoupleGrab` si vue invalide ; `bGrabbing` + 2 conditions `!bGrabbing` ; témoin ; deux fonctions nouvelles). Hunks du .h : 2 (déclarations anticipées, bloc de membres). | Hunk | Dans le périmètre de l'IMPL § 1 | |---|---| | Constantes `PelvisSocket`, `RoomFloor`, 120 px, 5 px, 0,3, 30 cm, 5000, +150, −400 (l. 119-129) | oui | | `TickSexCamera` : `ReleaseCoupleGrab` (l. 503, 515), remise de `bCursorWarned` (l. 504-507), `PC` non const (l. 511), `TickCoupleMouse` avant ZQSD (l. 535), `!bGrabbing` sur les deux branches (l. 537, 559) | oui | | Témoin : `bButtons`, G, D, prise, lacet, ligne quand seul un bouton est tenu (l. 576-587) | oui | | `ReleaseCoupleGrab` (l. 590-596), `TickCoupleMouse` (l. 598-786) | oui | | .h : `class AActor;`, `class UGameInstance;`, 2 fonctions, 14 membres | oui | Inchangé : toutes les autres lignes de B2r (CharGen, témoins C1, C2, occlusion, touche C) et de B2p-C (formules d'orbite, distances, POV) ; les deux seules modifications de leurs branches sont les `!bGrabbing` déclarés. **Aucun ajout non demandé** : `Query.AddIgnoredActor(View)` (l. 620), en plus de la liste de la SPEC (couple, `Wall`, `CamRoom`), est inoffensif et cohérent (la caméra en vue ne doit pas être touchée par son propre tracé). ### Contrôle 3 : relecture C++ UE 5.8 Sources lues : `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\…`. | Élément | Preuve moteur | Verdict | |---|---|---| | `GetMousePosition(float&, float&) const` | `PlayerController.h:1623` | PASS-STATIC | | `DeprojectMousePositionToWorld(FVector&, FVector&) const` | `PlayerController.h:731` | PASS-STATIC | | `ProjectWorldLocationToScreen(FVector, FVector2D&, bool) const` | `PlayerController.h:742` | PASS-STATIC | | `AddIgnoredActors(const TArray&)` | `CollisionQueryParams.h:257` (autres surcharges : `const AActor*`, `TWeakObjectPtr`, sans ambiguïté) | PASS-STATIC | | `AddIgnoredActor(const AActor*)` avec `View`, `Wall`, `CamRoom` | `CollisionQueryParams.h:243` | PASS-STATIC | | `SCENE_QUERY_STAT(LPCoupleDrag)` + `true` | `CollisionQueryParams.h:39` : deux arguments (`FName`, `TStatId`) ; même forme que `LPDirectControlSubsystem.cpp:1024` | PASS-STATIC | | `UWorld::LineTraceSingleByChannel(...) const` | `World.h:2161`, appelé via `const UWorld*` | PASS-STATIC | | `FMath::PointDistToSegment` | `UnrealMathUtility.h:2017` (rend `float`, comparé à `const float`) | PASS-STATIC | | `FVector(FVector2D, 0.f)` | `Vector.h:176` explicite, `0.f` → `double` | PASS-STATIC | | `FVector MousePoint(float, float, float)` | `Vector.h:168` `TVector(T,T,T)` | PASS-STATIC | | `FVector2D::Distance` | `Vector2D.h:397` statique, rend `T` | PASS-STATIC | | `EKeys::LeftMouseButton/RightMouseButton` | `InputCoreTypes.h` via `GameFramework/PlayerInput.h` (déjà inclus, l. 17 ; `LeftShift` déjà utilisé de la même façon) | PASS-STATIC | | `UPrimitiveComponent` / `Bounds` / `FBox` | `Bounds` est public (`SceneComponent.h:127-129`) ; `GetBox() const` (`BoxSphereBounds.h:226`) ; `PrimitiveComponent.h` arrive par `Components/SkeletalMeshComponent.h` (l. 9) ; `Box` est un `UBoxComponent` (`LPOutdoorRoomSubsystem.cpp:32, 233`) | PASS-STATIC | | `Cast(const AActor*)` | `Casts.h:89` : qualificatifs recopiés, rend `const ACharacter*` ; précédent `LPSecondScreenSubsystem.cpp:36` | PASS-STATIC | | Os `pelvis` | existe dans le squelette (`STD-SCN-DIAG.md` l. 50 ; `LPDebugSubsystem.cpp:523`) ; sinon `GetSocketLocation` rend l'origine du maillage (pas de plantage) | PASS-STATIC | | `.h` sans include d'`AActor` | `TWeakObjectPtr` avec déclaration anticipée : même schéma que `TWeakObjectPtr Viewport` (.h l. 108, déjà compilé en B2c) ; `GrabCamera.Get/Reset/=` sont dans le .cpp où `AActor` est complet (`GameFramework/Actor.h`, l. 13) | PASS-STATIC | | Membre non `UPROPERTY` dans `UCLASS` + `GENERATED_BODY` | accepté : UHT ignore ce qui n'est pas balisé ; tous les membres de ce .h sont déjà dans ce cas (`Root`, `Viewport`, `bShown`…) | PASS-STATIC | | `UGameInstance` / `UObject` dans une signature privée | `class UGameInstance;` ajoutée ; `UObject` vient de `Subsystems/GameInstanceSubsystem.h` | PASS-STATIC | | Lambdas `[&]` | captures `World`, `PC`, `Query`, `Pivot` (référence const) : durée de vie ≥ lambda, jamais stockées ; `Out`, `OutZ` en référence ; `-> bool` explicite | PASS-STATIC | | Types float/double | `GrabHeight`, `SurfaceZ`, `Target`, `Delta` en `double` ; `MouseX/Y`, `GrabTurned`, `GrabLastMouseX` en `float` ; `FMath::Clamp` aux 3 arguments `double` (`Target.X`, `Box.Min.X + Margin`, `Box.Max.X - Margin`) ; ternaire du témoin `double : double` | PASS-STATIC | | Const-correctness | `PC` passe non const (nécessaire pour `WriteBool(UObject*)`, l. 784) ; `Room` `const UObject*` vers `ReadObject(const UObject*)` ; boucle de déplacement `AActor*` sur `const TArray&` (éléments `AActor* const`) ; `Mesh` const : `GetSocketLocation` est const | PASS-STATIC | | Ombrages de noms (C4456/C4458) | `SurfaceZ`, `Aim`, `Rotation`, `Person`, `Hit` redéclarés seulement dans des portées sœurs, jamais imbriquées ; aucun local ne porte le nom d'un membre | PASS-STATIC | | Aides de `LPCameraModeLocal` qualifiées | `Grep` des noms d'aides et de constantes précédés d'autre chose que `::` : seules l. 43-253 (définitions dans l'espace) et l. 673 (commentaire) ; **aucun appel non qualifié** ; `PelvisSocket`, `RoomFloor`, `CursorTraceLength`, `CoupleSelectRadius` n'existent dans aucun autre fichier de `Source/` ; `namespace LPCameraModeLocal` défini une seule fois (l. 30) : pas d'ambiguïté en compilation unitaire avec `LPDirectControlSubsystem.cpp` | PASS-STATIC | Formats `UE_LOG` (spécificateurs comptés dans la chaîne, arguments comptés dans l'appel) : | Ligne du journal | Spécificateurs | Arguments | | |---|---|---|---| | « Sexe : touches … » (l. 581-587) | 17 (`%+.0f`×4, `%d`×6, `%s`, `%d`×2, `%.0f`×4 ; le lacet est le 17e) | 17 : `Forward`, `Right`, 2 `AxisValue`, `bShift`, `MoveObject`, `MoveCamera`, `bLeftWasDown`, `bRightWasDown`, `CoupleGrabMode`, `*GetNameSafe`, `ReadInt`, `Couple.Num()`, `Pivot.X/Y/Z`, `Yaw` | égal | | « couple pris (%s) a (%.0f ×3), hauteur … %s » | 5 | 5 : ternaire `TEXT`, `X`, `Y`, `Z`, ternaire `*FString::Printf(...)` / `TEXT` | égal ; le temporaire `FString` vit jusqu'à la fin de l'expression complète de l'appel de journal ; les deux branches du ternaire sont `const TCHAR*` | | « couple pose a (%.0f ×3), lacet %.0f » | 4 | 4 | égal | | « couple tourne de %+.0f degres, lacet %.0f » | 2 | 2 (`GrabTurned` float, `Yaw` double) | égal | | « curseur sans position … (vue %s) » | 1 | 1 | égal | ### Contrôle 4 : logique et gardes de Big Boss | Id | Garde | Preuve (ligne) | Verdict | |---|---|---|---| | (a) | Rien hors sexe ou pendant un menu, prise levée | `TickSexCamera` l. 500-509 : `!SexOn` ou `bMenuOnScreen` → `ReleaseCoupleGrab()` puis retour (aucune lecture de la souris) ; vue invalide l. 513-517 idem. `ReleaseCoupleGrab` (l. 590-596) remet mode, caméra figée, `bLeftWasDown`, `bRightWasDown` à zéro ; `bCursorWarned` remis à faux hors sexe seulement (l. 504-507) : « une ligne par scène » | PASS-STATIC | | (b) | Mode Scene seulement | l. 609 : `Couple.Num() > 0 && !Couple.Contains(View) && !ReadBool(PC, POVOn)` ; sinon `CoupleGrabMode = 0`, caméra relâchée, `return false` (l. 610-614). L'état des boutons est mis à jour **avant** ce test (l. 603-606) : passer de POV à Scene bouton tenu ne crée pas de front | PASS-STATIC | | (c) | Aucun clic consommé | `TickCoupleMouse` ne lit que `PlayerInput->IsPressed` (l. 599-602) ; la seule écriture sur le PC est `WriteBool(PC, MoveCamera, false)` (l. 784), jamais exécutée hors prise (tous les `return false` de la fonction la précèdent : l. 614, 662, 671, 693, 727). Aucun `Consume`, `SetInputMode`, `SetIgnore…` | PASS-STATIC | | (d) | Borne au sol de `Room.Box` avec marge | l. 737-749 : AABB de `Floor->Bounds` (l. 741), marge 30 cm, `Clamp` en X et Y ; `Floor` absent ou trop petit (≤ 2 marges) → pas de borne. Réserve D-3 | PASS-STATIC | | (e) | Rien avant 5 px | l. 732 : `bGrabMoved |= Distance(début, souris) >= 5` (verrou une fois franchi) ; le déplacement exige `bGrabMoved && TraceCursor` (l. 734) | PASS-STATIC | | (f) | Hauteur par tracé vertical, garde si pas d'impact | à la prise : `TraceDown(Pivot, SurfaceZ)` → `bGrabHasHeight`, `GrabHeight = Pivot.Z - SurfaceZ` (l. 704-705) ; au glisser : `Target.Z = SurfaceZ + GrabHeight` seulement si `bGrabHasHeight && TraceDown(Target, …)` (l. 751-754) ; sinon Z actuel gardé. Rayon du curseur sans impact → `TraceCursor` faux → le couple ne bouge pas à cette image (SPEC § 7.11a) | PASS-STATIC | | (g) | Rotation autour du pivot | l. 762-775 : `Offset.RotateAngleAxis(Turn, UpVector)` autour de `Pivot` (moyenne des positions, invariante par rotation) et `Yaw += Turn` pour chaque personne, `Turn = ΔX × 0,3` ; sens cohérent avec le lacet (rotation positive autour de Z = lacet positif) | PASS-STATIC | | (h) | Caméra figée pendant la prise, libérée au relâcher et hors sexe | l. 780-783 : pose mémorisée (l. 700-702) remise à chaque image tant que la prise dure ; relâcher : `GrabCamera.Reset()` (l. 726) ; hors sexe, hors Scene, vue invalide : `Reset` (l. 593, 613) ; ZQSD coupé par `!bGrabbing` (l. 537, 559). Réserves D-5 (ordre d'image) et D-6 (caméra remplacée en cours de prise) | PASS-STATIC | | (i) | Aucune déréférence nulle | `PC->PlayerInput` testé (`Input &&`, l. 599-601) ; `GI` non nul par l'appelant (l. 500) ; `World` testé (l. 610) ; `View` valide par l'appelant (l. 513) ; `Room` nul accepté par `ReadObject` ; `Mesh` testé (l. 679-681) ; `Floor` par `IsValid` (l. 739) ; `GrabCamera.Get()` testé (l. 780). **`Couple[0]`** : l. 716 (branche « relâcher ») n'est atteinte qu'après le `return false` de l. 610-614, donc `bScene` vrai, donc `Couple.Num() > 0` (l. 609) ; l. 587 gardé par `Couple.Num() > 0 ?`. Les éléments de `Couple` sont déjà filtrés `IsValid` par `ReadActors` (l. 191). Les boucles l. 676, 756 et 769 n'itèrent qu'un tableau non vide. `Wall` et `CamRoom` testés par `if (AActor* … = Cast(…))` (l. 621, 625) | PASS-STATIC | | (j) | Conflits avec flèches (P2) et Maj + molette seulement pendant une prise | aucun code ne touche aux flèches ni à la molette (`bSexWheelZoom` reste faux) ; pendant un glisser, la position est réécrite à chaque image, donc remplace un appui de flèche (déclaré, IMPL § 2). Hors prise : aucune écriture. Réserve D-1 (Maj + clic) levée à la reprise : § 7 | PASS-STATIC | Rattachement aux essais V7 (non rejoués) : CAM-58 = (e)(f) ; CAM-59 = (g)(h) ; CAM-60 = (d) ; CAM-61 = clic hors couple (l. 691-693 : `return false` sans prise, aucune écriture) ; CAM-62 = (b) ; CAM-63 = (a) (aucun mouvement après la sortie : le déplacement n'est atteint que dans `TickCoupleMouse`, que `TickSexCamera` n'appelle plus hors sexe). ### Contrôle 5 : cohérence SPEC / IMPL | Exigence SPEC § 7.11 | IMPL / code | Verdict | |---|---|---| | Sélection par projection, 120 px, segment bassin-tête, sans tracé de collision | l. 673-694 ; `Mesh` absent → position de l'acteur | conforme | | Tracé vertical à la prise (Visibility, complexe ; couple, `Wall`, `CamRoom` ignorés), dz gardé | l. 618-627, 704-705 | conforme (`View` ignoré en plus) | | Glisser : rayon du curseur, nouveau XY = impact + décalage, borné, Z = surface + dz | l. 706-707, 730-757 | conforme | | Pas de mouvement sans impact | l. 734 | conforme | | Clic droit = rotation 0,3°/px autour du pivot, caméra figée | l. 762-775, 780-784 | conforme | | Clic hors couple : comportement actuel inchangé | l. 691-693 | conforme | | Maj + molette reste disponible | IMPL § 2 : non joué pendant une prise de déplacement | écart déclaré, acceptable | | « La rotation d'origine (Maj + bouton + souris) reste » (§ 7.11b) | initialement : `MoveCamera` forcé à faux sans test de Maj (D-1) ; après reprise : conforme (§ 7) | levé | | Retour automatique à la sortie, sélection levée sans pose | l. 500-509 | conforme | | Volume ≈ 200 lignes | +228 / −6 (.cpp), +28 (.h) | conforme | ## 4. Diff lu en entier | Fichier | Compte | Statut git | Lecture | |---|---|---|---| | `Source/LifePlay/LPCameraModeSubsystem.cpp` | VAL initiale : +228 / −6 ; 820 → 1042. Après reprise : +230 / −6 ; 820 → 1044 | `??` (à ajouter, A) | `diff -u` entier lu, 7 hunks, aucun hors périmètre | | `Source/LifePlay/LPCameraModeSubsystem.h` | +28 / −0 ; 84 → 112 (inchangé par la reprise) | `??` (A) | `diff -u` entier lu, 2 hunks | Recompte par une seconde méthode (`git diff --no-index --numstat`) égal au recompte `wc -l` : 228 − 6 = +222 = 1042 − 820 ; 28 = 112 − 84. Fins de ligne LF identiques à l'instantané ; aucune trace de modification par script dans le diff. ## 5. Verdict **PASS-STATIC (final, après reprise).** Aucun défaut bloquant. D-1 est levé (§ 7). Réserves non bloquantes D-2 à D-9 ci-dessous ; la compilation (COMMUN-8) et les essais CAM-58 à CAM-63 restent à faire, aucun n'est compté comme passé. Défauts bloquants : **aucun**. Défauts non bloquants : | Id | Ligne (état initial) | Constat | Suite proposée (non appliquée) | |---|---|---|---| | D-1 | 598-786, 784 | **LEVÉ à la reprise (§ 7).** Maj + bouton tenu sur le couple déclenchait aussi une prise et mettait `MoveCamera` à faux : la rotation d'origine (Maj + bouton + souris, SPEC § 7.1) était masquée près du couple, contre § 7.11b | sans objet | | D-2 | 784 | `MoveCamera` forcé à faux : la voie « `Manual` + souris → `AnimatingSpeed` quand `MoveCamera` est faux » (SPEC § 7.11b) est morte au dump, mais n'a pas été revérifiée | V7 : `AnimatingSpeed` inchangé pendant un glisser | | D-3 | 737-749 | Si le couple part hors de l'AABB de `Room.Box`, le premier pas le ramène d'un coup au bord (− 30 cm) | V7 CAM-60, dans une pièce et dans l'extérieur | | D-4 | 707 | Rayon du curseur sans impact à la prise : décalage nul, puis le couple saute sous le curseur au premier pas | cas rare (viser le ciel) ; V7 | | D-5 | 535, 780-783 | Le ticker passe avant le tick du monde : le PC peut tourner la caméra une image avant que la pose soit remise (image de décalage possible au clic) | V7 CAM-58/59 : tremblement de caméra à l'appui | | D-6 | 700, 780-783 | Si la vue change de caméra sans passer par POV pendant une prise, la caméra mémorisée est remise à sa pose, pas la nouvelle vue | négligeable ; levée au relâcher | | D-7 | 590-596, 603-606 | `ReleaseCoupleGrab` remet les « était enfoncé » à faux : un bouton déjà tenu à la fermeture d'un menu compte comme un nouveau front | négligeable (les boutons de menu agissent au relâcher) | | D-8 | 618-627, 704, 733-734 | Tracés `Visibility` complexes : le rayon du curseur et le départ du tracé vertical (pivot + 150) peuvent toucher un plafond ou un baldaquin avant le lit | V7 CAM-58 ; la ligne « hauteur sur la surface N cm » donne dz | | D-9 | 576-587 | Le témoin écrit une ligne par seconde dès qu'un bouton de souris est tenu pendant le sexe, même hors couple | déclaré (IMPL § 1) ; bruit de journal seulement | Établi / estimé / incertain : - **Établi** : MD5, lignes, CR, périmètre du diff, signatures et surcharges du moteur 5.8 (lignes citées), comptes de spécificateurs, absence d'appel non qualifié, gardes (a) à (j) par lecture, reprise D-1 (§ 7). - **Estimé** : compilation propre (aucune erreur trouvée par lecture ; MSVC non lancé) ; sens de rotation du lacet. - **Incertain** : tout comportement en jeu (D-2 à D-8), absence de collision de la sélection avec un clic d'interface, `Room.Box` couvrant le sol des pièces jouées. Essai qui trancherait : COMMUN-8 puis CAM-58 à CAM-63. ## 6. Points à confirmer par l'utilisateur - [x] D-1 : Maj + clic sur le couple : levé (aucune prise avec Maj ; la rotation d'origine garde la main). À observer tout de même en V7 : Maj + bouton + souris tourne le couple près du couple. - [ ] V7 : « curseur sans position » absent du journal en mode Scene (PC-7). - [ ] V7 : un clic gauche sur le couple ouvre-t-il aussi une interaction de Room ? Un clic droit ouvre-t-il un menu de widget ? - [ ] V7 : CAM-58 (hauteur sur le lit, caméra immobile), CAM-59 (rotation sur place), CAM-60 (arrêt à 30 cm du bord), CAM-61 (orbite hors couple), CAM-62 (touche C), CAM-63 (sortie bouton tenu). - [ ] COMMUN-8 : compilation (0 erreur, avertissements nouveaux listés) avant tout essai en jeu. ## 7. Reprise : revérification de D-1 (Maj + clic), 2026-10-14 Annonce du coordinateur : `.cpp` `D60632646BF1CF7D200E3BEA833D8D72`, 1044 lignes ; `.h` inchangé `6585D7F6…`. **Recompte** (`md5sum`, `wc -l`, `tr -cd '\r' | wc -c`, `git diff --no-index --numstat`) : ``` d60632646bf1cf7d200e3bea833d8d72 *LPCameraModeSubsystem.cpp 1044 CR=0 6585d7f67a7be3aea377b8e93cfa14d8 *LPCameraModeSubsystem.h 112 CR=0 numstat instantané → actuel : .cpp 230 / 6 (VAL initiale : 228 / 6, soit + 2) ; .h : diff -u de 45 lignes, MD5 inchangé ``` | Élément | Annoncé | Recompté | Verdict | |---|---|---|---| | MD5 .cpp | `D60632646BF1CF7D200E3BEA833D8D72` | `D60632646BF1CF7D200E3BEA833D8D72` | confirmé | | Lignes .cpp | 1044 (avant 1042) | 1044 | confirmé | | MD5 / lignes .h | `6585D7F6…`, 112 | `6585D7F67A7BE3AEA377B8E93CFA14D8`, 112 | confirmé | | CR | 0 | 0 (.cpp et .h) | confirmé | **Le diff ne diffère du précédent que par ces lignes** : preuve par reconstruction. Sur le fichier actuel, retrait des l. 660-661 et remise de la l. 662 à `if (!bLeftPressed && !bRightPressed)` (`sed` en lecture, sortie vers `md5sum`, aucun fichier écrit) : ``` 3db310dc025f50b9bc5b0681be84b15e *- (= MD5 de la VAL initiale, 3DB310DC…) ``` La reprise se réduit donc exactement à ces trois lignes (l. 660-662) ; tout le reste, y compris le diff lu en § 4, est identique octet pour octet à l'état déjà validé. **Lignes ajoutées** (`cat -A`, tabulations seules, pas de CR), dans `TickCoupleMouse`, branche `CoupleGrabMode == 0` : ``` 660 // Maj tenu : la voie d'origine (Maj + bouton + souris, Room) garde la main, aucune prise. 661 const bool bShiftHeld = Input && (Input->IsPressed(EKeys::LeftShift) || Input->IsPressed(EKeys::RightShift)); 662 if ((!bLeftPressed && !bRightPressed) || bShiftHeld) ``` Conforme à l'annonce (commentaire, `bShiftHeld`, condition). **Compilation à l'œil** (PASS-STATIC) : - `Input` est le `UPlayerInput*` déclaré en tête de fonction (l. 599) : dans la portée. - `UPlayerInput::IsPressed(FKey) const` et `EKeys::LeftShift/RightShift` : mêmes appels que `TickSexCamera` l. 575 (déjà compilés en B2p-C). - `Input && (bool || bool)` : pointeur converti en `bool` par `&&`, résultat `bool` pour un `const bool`. Pas de macro, pas de virgule exposée. - Parenthèses de la condition équilibrées ; `bShiftHeld` n'est déclaré qu'une fois dans `TickCoupleMouse` (aucun ombrage : le `bShift` de `TickSexCamera` est une autre fonction). - Variable utilisée : pas d'avertissement « variable inutilisée ». **D-1 levé** (lecture de la fonction, état final) : - Maj tenu et front montant d'un bouton avec `CoupleGrabMode == 0` : le `return false` de la l. 664 est pris avant toute écriture. Aucune prise, donc ni `CoupleGrabMode`, ni `GrabCamera`, ni mémorisation de la pose de la caméra. - `MoveCamera` n'est écrit qu'à la fin de la fonction (l. 786 après décalage), après les `return false` : il n'est donc pas touché. La chaîne d'origine (bouton → `StartMoveCamera` → `MoveCamera` → Room avec Maj → rotation) reste intacte. - La caméra n'est pas figée. ZQSD n'est pas coupé (`bGrabbing` faux). - Les « était enfoncé » sont mis à jour avant (l. 603-606) : relâcher Maj bouton tenu ne crée pas de front tardif, donc pas de prise en cours de geste. - Maj + clic hors couple : inchangé (aucune prise, comme avant). - Sans Maj : comportement identique à la VAL initiale (le test ajouté vaut faux). - Cas limite (V7, non bloquant) : Maj appuyé **après** le début d'une prise. Le test n'existe que pour `CoupleGrabMode == 0`, donc la prise continue (le glisser reste cohérent, `MoveCamera` reste à faux). La rotation d'origine ne démarre alors pas, ce qui est le comportement voulu pour un geste commencé sans Maj. **Section « Reprise » de `CAM-B2p-D-IMPL.md`** (l. 66-77) relue : elle décrit « deux lignes de plus dans la sélection » et liste D-2 à D-9 comme laissés en l'état. Cohérent avec le diff : 2 lignes de plus (la condition est modifiée en place), D-2 à D-9 non touchés (leurs lignes sont identiques, décalées de + 2 après la l. 660). Le tableau MD5 en tête de l'IMPL n'a pas été recontrôlé au-delà de ce qui est donné ici. **Numéros de ligne** : après la l. 660, les lignes citées en §§ 2 à 6 sont à lire + 2 (ex. `WriteBool(PC, MoveCamera, false)` l. 784 devient l. 786, fin de `TickCoupleMouse` l. 786 devient 788). **Verdict de la reprise : PASS-STATIC.** D-1 levé. Verdict final du lot : **PASS-STATIC**, aucun défaut bloquant ; D-2 à D-9 non bloquants à observer en V7 ; compilation (COMMUN-8) et essais CAM-58 à CAM-63 à faire. Fichiers à commiter (dépôt : jeu, branche ue5.8) : - A `Source/LifePlay/LPCameraModeSubsystem.cpp` (MD5 `D6063264…`) - A `Source/LifePlay/LPCameraModeSubsystem.h` - A `Docs/Migration/etat/CAM/CAM-B2p-D-IMPL.md` - A `Docs/Migration/etat/CAM/CAM-B2p-D-VAL.md` (ce livrable) Tours : 28 / 40