Fiche CAM-B2p-C-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-C-VAL : ZQSD pour la caméra pendant le sexe, molette en réserve, témoin des touches (VAL statique, 2026-10-14, reprise incluse) Verdict : **PASS-STATIC**, confirmé après la reprise (voir « Reprise » en fin de document). Aucune compilation, aucun éditeur, aucun MCP, aucun jeu : tout est lu. Défauts bloquants : 0. Défauts non bloquants : 6 à la première passe ; après la reprise, 3 sont levés (2, 3, 4) et 3 restent (1, 5, 6). La compilation et les essais V7 restent à faire sous jeton UE. Les numéros de ligne des § 2 à 6 et des Constats valent pour la **première version** (`.cpp` `E5DA8299…`, 817 lignes). Dans la version actuelle (`8E35E07A…`, 820 lignes), ils sont décalés de +1 après la ligne 116 et de +3 après la ligne 484. ## 1. Fiches amont relues | Fiche | Lue | Note | |---|---|---| | `CAM-B2p-SPEC.md` (P1, P2, P3, P3 bis, § 3 à 6) | oui, lignes 1-160 | P3 aux lignes 73-91, P3 bis aux lignes 51-55 | | `CAM-B2p-C-IMPL.md` | oui, en entier, puis relue à la reprise (lignes 44-65) | l'IMPL n'a pas les sections « Points à confirmer en VAL » et « Fichiers à commiter » du gabarit IMPL : écart de gabarit (Constats 5) | | `CAM-B2p-R-FICHE.md` | non relue en détail | pour info seulement (Room, MCP) ; hors périmètre de cette VAL | | `CAM-B2r-VAL.md` (état d'avant) | non rouverte | l'état B2r est établi par l'égalité MD5 de l'instantané avec les valeurs annoncées (§ 3, contrôle 1) | ## 2. Points à confirmer des IMPL L'IMPL ne numérote pas de points à confirmer. Je reprends ses déclarations vérifiables, une par ligne. | Id | Déclaration de l'IMPL | Résultat | |---|---|---| | C-1 | MD5 avant `124336D3…` / `61A3E062…`, après `E5DA8299…` / `9E20981F…` | PASS (§ 3, contrôle 1) ; après la reprise : `8E35E07A…` / `9E20981F…`, PASS (Reprise) | | C-2 | `.cpp` 662 → 817 lignes, `.h` 77 → 84 lignes | PASS (`wc -l` : 662, 817, 77, 84) ; après la reprise : 820 et 84, PASS | | C-3 | Fins de ligne LF, 0 CR | PASS (§ 3, contrôle 3) ; reprise : 0 CR, PASS | | C-4 | Écart 1 : `MenuOnScreen` gardé dans `bMenuOnScreen` (0,1 s) au lieu d'`IsMenuOpen` seul | PASS : `MenuOnScreen` appelle `IsMenuOpen` en premier (`.cpp:248`). Écart acceptable, plus large donc plus sûr. Contrepartie : jusqu'à 0,1 s de retard à l'ouverture d'un menu : acceptable | | C-5 | Écart 2 : P3 bis écrit mais désactivé (`bSexWheelZoom = false`) | PASS (`.cpp:114` ; `Wheel` vaut 0 à `.cpp:498`) | | C-6 | « Sans P1 (Room), ZQSD n'agit pas en mode Scene ; le POV marche sans P1 » | PASS par lecture : en Scene, la condition `View == SceneCamera` (`.cpp:513`) est fausse si la vue est `CamRoom`. Effet réel : reporté (V7, avec B2p-R) | | C-7 | « Compilation et B2p-R non faites » | confirmé : aucun journal de build ; reporté (BUILD sous jeton UE) | | C-8 | Signe « D tourne vers la droite (angle = −côté × vitesse × dt) » | voir § 3, contrôle 5 : le signe est juste pour « la caméra se déplace vers sa droite autour du pivot » ; le libellé était ambigu pour le lacet, désormais déclaré dans l'IMPL (Reprise) ; ressenti reporté (V7-1) | ## 3. Critères rejoués | N° | Critère | Commande / lecture | Résultat brut | Verdict | |---|---|---|---|---| | 1 | MD5 avant et après | `md5sum` sur l'instantané `D:\Game\LifePlay_Backup_5.5\CAM-B2p-C\Source\LifePlay\` et sur `Source/LifePlay/` | avant : `124336d3293ef06befcf076d10956234` (.cpp), `61a3e06280bf7226adb52ecfdd9173de` (.h). Après (première version) : `e5da82992ac18471e51acf1798b7f0e2` (.cpp), `9e20981f8c768371a0935e2c7ce9e134` (.h) | PASS : les quatre égalent les valeurs annoncées | | 2 | Diff dans le périmètre | `diff -u` instantané → actuel, lu en entier (190 lignes pour le .cpp, 17 pour le .h) | .cpp : 4 hunks, +156 / −1 (net +155 = 817 − 662). La seule suppression est `(void)DeltaTime;` remplacée par `TickSexCamera(DeltaTime);` (`.cpp:477`). .h : 1 hunk, +7 / 0 (net +7 = 84 − 77) | PASS : voir le détail au § 4 | | 3 | Fins de ligne | `tr -cd '\r' < fichier \| wc -c` | .cpp actuel : 0 ; .h actuel : 0 ; .cpp instantané : 0 ; .h instantané : 0 | PASS | | 4 | Relecture C++ UE 5.8 | lecture du .cpp et des en-têtes du moteur (`D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime`) | voir ci-dessous | PASS-STATIC | | 5 | Logique (a) à (h), signe de D | lecture de `TickSexCamera` (`.cpp:481-561`) | voir ci-dessous | PASS-STATIC | | 6 | Cohérence SPEC / IMPL | SPEC lignes 51-55 et 73-91 contre l'IMPL | voir ci-dessous | PASS avec écarts mineurs (déclarés depuis) | ### Contrôle 4 : C++ UE 5.8 (preuves) | Point | Preuve | Résultat | |---|---|---| | `FVector::RotateAngleAxis` | `Core/Public/Math/Vector.h:769` : `RotateAngleAxis(const T AngleDeg, const TVector& Axis) const` ; `Angle` est un `float`, converti en `double` à l'appel (`.cpp:519`) | OK | | `FMath::Clamp` avec `double` | `.cpp:524-525` : premier argument `Distance - float*float*float - float*float` donc `double` (car `Distance` est `double`), bornes en `static_cast` ; trois `double`, un seul `T` | OK | | `FMath::Clamp` du tangage | `.cpp:543-544` : `Rotation.Pitch` (`double` en LWC) `+ float`, bornes `-static_cast(...)` et `static_cast(...)` | OK | | `FRotator` en `double` | `Rotation.Yaw += Angle` (`:531`) et `+= Right * ...` (`:542`) : `double += float`, sans rétrécissement | OK | | `SetActorLocationAndRotation` | `Engine/Classes/GameFramework/Actor.h:1701` : `(FVector, FRotator, bool=false, FHitResult*=nullptr, ETeleportType=None)`. `View` est un `AActor*` non const (`.cpp:491`) | OK | | `USceneComponent::SetWorldRotation` | `Components/SceneComponent.h:575-576` : surcharges `FRotator` et `FQuat` ; l'argument est un `FRotator` : pas d'ambiguïté. `Look` est un `USceneComponent*` non const | OK | | `FIntProperty` | `UnrealType.h:1663` : `GetPropertyValue_InContainer(void const*, int32=0) const` renvoie `int32 const&` ; `Prop ? ... : -1` donne `int` | OK | | `FScriptArrayHelper`, `FObjectPropertyBase` | `ReadActors` (`.cpp:165-184`) reprend le motif de `ArrayContains` (`.cpp:220-237`, B2r, déjà validé) | OK | | `PlayerInput->IsPressed` / `GetKeyValue` sur un PC const | `PlayerInput.h:745` et `:757`, les deux `const` ; `PlayerInput` est un pointeur membre : sur `const APlayerController*` il reste `UPlayerInput* const`. Même motif déjà présent dans `TickSwitchKey` (`.cpp:461-465`) | OK | | `AxisValue` | corps identique à `LPDirectControlSubsystem.cpp:167-186` (relu côte à côte) | OK | | `EKeys` : include nécessaire ? | `EKeys::LeftShift` / `RightShift` : `InputCore/Classes/InputCoreTypes.h:455-456` ; `PlayerInput.h:14` inclut `InputCoreTypes.h`, et `PlayerInput.h` est inclus (`.cpp:17`) | OK, aucun include à ajouter | | `TArray::Contains(AActor*)` | `Couple` est `const TArray` ; `Contains` est `const` et prend le type de l'élément ; `View` est un `AActor*` (`.cpp:535`) | OK | | `View == SceneCamera` | `AActor*` contre `const AActor*` : comparaison de pointeurs valable (`.cpp:513`) | OK | | `GetViewTarget` sur un PC const | `PlayerController.h:1911` : `virtual AActor* GetViewTarget() const override` | OK | | `Pivot /= Couple.Num()` | `TVector::operator/=(T)` ; `int32` converti en `double`. Gardé par `Couple.Num() > 0` (`.cpp:507-509`) | OK | | `GetAxisMappings`, `IsDigital` | `InputSettings.h:318` ; `InputCoreTypes.h:81` | OK | | `UE_LOG` (`.cpp:557-560`) | 13 spécificateurs : `%+.0f` ×4, `%d` ×3, `%s` ×1, `%d` ×2, `%.0f` ×3. 13 arguments : `Forward`, `Right`, `AxisValue` ×2 (quatre `float`, promus en `double`) ; `bShift ? 1 : 0`, deux `ReadBool ? 1 : 0` (`int`) ; `*GetNameSafe(View)` (`TCHAR*`) ; `ReadInt` (`int32`) ; `Couple.Num()` (`int32`) ; `Pivot.X`, `.Y`, `.Z` (`double`) | OK : types et nombre concordent | | Ombrage / redéfinition | `Rotation` est déclaré dans deux blocs disjoints (`.cpp:530` et `:541`) ; aucun membre de la classe ne porte les noms `View`, `Room`, `Pivot`, `Couple`, `Look`, `Forward`, `Right` (`.h` relu en entier) | OK | | Avertissement sur condition constante | `bSexWheelZoom ? ... : 0.f` (`.cpp:498`) : ternaire sur constante ; MSVC C4127 coupé (`VCToolChain.cs:338`), C4702 coupé dans le cas courant (`VCToolChain.cs:1396-1407`) | OK, incertain à la compilation (Constats 6) | | Collision de noms en compilation unitaire | `Grep AxisValue` dans `LPCameraModeSubsystem.cpp` : définition `:187` dans `namespace LPCameraModeLocal` (ouvert `:30`, fermé `:300`) ; appels `:496`, `:497`, `:498`, `:550` (×2), `:558` (×2), tous préfixés `LPCameraModeLocal::`. `ReadInt` : appel `:560`, préfixé. `ReadActors` : appel `:501`, préfixé. Les nouveaux noms (`AxisMoveForward`, `GISexPersons`, `RoomMoveObject`, etc.) ne sont utilisés que préfixés. Aucun appel non qualifié | OK : pas de C2668 attendu | ### Contrôle 5 : logique | Point | Lecture | Verdict | |---|---|---| | (a) rien ne bouge si `SexOn` faux ou menu | `.cpp:485-488` : retour avant toute lecture de touche ou écriture si `!GI`, `SexOn` faux ou `bMenuOnScreen`. Seul `SexKeysCooldown` est décrémenté avant (`:483`), sans effet visible | OK | | (b) mode Scene seulement si vue = `Room.SceneCamera` et couple non vide | `.cpp:513` : `bKeys && Couple.Num() > 0 && View == SceneCamera`. `Room` nul : `ReadObject(nullptr)` renvoie `nullptr` (`.cpp:127`) donc `SceneCamera` nul, et `View` valide ne l'égale jamais | OK | | (c) bornes seulement avec Z/S (ou molette) | `.cpp:522-526` : `Clamp` dans `if (Forward != 0 \|\| Wheel != 0)`. L'orbite seule garde `Distance = Offset.Size()` (une rotation conserve la norme) | OK, mais voir Constats 1 (saut au premier appui) | | (d) direction nulle protégée | `.cpp:527-533` : `GetSafeNormal()` puis `if (!Direction.IsZero())` ; sinon ni position ni lacet ne changent | OK | | (e) POV | `.cpp:535-547` : `Couple.Contains(View)`, `Look` lu par `ReadObject(View, FirstPersonSM)` puis `IsValid(Look)` ; tangage `Clamp(..., -80, +80)` | OK | | (f) P3 bis désactivé | `bSexWheelZoom = false` (`.cpp:114`) donc `Wheel = 0.f` (`:498`) ; `AxisScroll` n'est pas lu | OK | | (g) témoin au plus une ligne par seconde | `.cpp:550-557` : retour si `SexKeysCooldown > 0` ou aucune touche (`:552-555`) ; sinon `SexKeysCooldown = 1.f` (`SexKeysLogInterval`, `:556`) puis une seule ligne. Le décompte se fait à chaque image (`:483`) | OK | | (h) déréférencements nuls | `GI` testé (`:485`) ; `World` testé (`:489`) ; `PC` et `View` testés (`:490-492`, `IsValid(View)`) ; `Room` : `ReadObject` / `ReadBool` acceptent un `nullptr` ; `PlayerInput` testé dans `AxisValue` (`:190`) et au témoin (`:551`) ; `Person` : `ReadActors` ne garde que des `IsValid` (`:178`) ; `Look` : `IsValid` (`:539`) | OK | | Signe de D | Voir ci-dessous | confirmé pour le déplacement ; libellé ambigu pour le lacet (déclaré depuis dans l'IMPL) | **Signe de D (raisonnement vectoriel).** - Entrée : `MoveRight` vaut +1 sur D et −1 sur Q (`Config/DefaultInput.ini:158-160`). `MoveForward` vaut +1 sur Z/W et −1 sur S (`:155-157`). Z réduit bien la distance (`Distance − Forward × …`, `.cpp:524`) : cohérent. - Rotation : `RotateAngleAxisRad` (`Vector.h:1339-1362`), axe (0,0,1) : `x' = C·x − S·y`, `y' = S·x + C·y`. Un angle positif envoie +X vers +Y. En UE (X avant, Y droite, Z haut), c'est le sens horaire vu de dessus, le même que le lacet croissant. L'angle appliqué à la position (`:519`) et au lacet (`:531`) est le même : le cadrage est gardé. - Cas test : caméra à l'ouest du pivot, `Offset = (−d, 0)`, qui regarde +X ; sa droite est +Y. Pour D, `Angle = −vitesse × dt < 0`, donc `S < 0` et `y' = S·(−d) = −d·S > 0` : la caméra se déplace vers +Y, donc **vers sa droite**, autour du pivot. Son lacet diminue (la caméra se tourne vers la gauche pour garder le couple dans le champ), ce qui est normal pour une orbite. - Conclusion : le signe `Angle = −Right × vitesse × dt` est **juste** si « D » signifie « la caméra se déplace vers sa droite » (comme un pas de côté). Il est **inverse** si « D tourne vers la droite » signifie « le lacet de la caméra augmente » ; c'est ce que fait le POV (`Yaw += Right × …`, `:542`, regard vers la droite). À l'écran, les deux modes donnent le même effet (la scène défile vers la gauche quand on appuie sur D). À confirmer par l'essai V7-1 (sens ressenti), pas bloquant. ### Contrôle 6 : SPEC / IMPL | Élément SPEC | IMPL | Verdict | |---|---|---| | P3 : conditions `SexOn`, menu fermé, PC et vue valides | tenues (`:485-492`) ; menu par `bMenuOnScreen` (écart déclaré, acceptable) | OK | | Orbite 90°/s, dolly 200 cm/s, bornes 80 à 800 cm, pivot = moyenne de `SexPersons`, tableau vide = rien | constantes `.cpp:107-110` ; moyenne `:502-510` ; tableau vide : pas de Scene, pas de POV (`Contains` faux) | OK | | POV : `FirstPersonSM`, lacet Q/D, tangage Z/S à 90°/s, ±80° | tenu (`:535-547`). Écart : la SPEC dit `AddWorldRotation(0, lacet, 0)` et un tangage « autour de l'axe droit du composant ». L'IMPL fixe des angles d'Euler monde (`GetComponentRotation` puis `SetWorldRotation`). Équivalent tant que le roulis est nul | écart mineur, déclaré depuis dans l'IMPL (Reprise) | | Témoin : format `Z=%d Q=%d S=%d D=%d fleches=… couple a (…)` | format réel : `avant=%+.0f cote=%+.0f fleches=…/… Maj MoveObject MoveCamera ; vue ; index POV ; couple N a (x, y, z)` | écart mineur, déclaré depuis dans l'IMPL (Reprise) | | `.h` : « 2 membres (dernier temps de témoin, vitesse) » | 1 méthode + 2 membres (`bMenuOnScreen`, `SexKeysCooldown`), pas de vitesse (constante locale) | OK | | P3 bis désactivé par constante | tenu | OK | | « Environ 120 lignes » | +156 lignes dans le `.cpp`, +7 dans le `.h` (dont constantes et commentaires) | OK | | Aucune lecture `Accessed None` | réflexion avec contrôle de nullité partout (voir (h)) | OK | ## 4. Diff lu en entier | Fichier | Compte | Contenu | |---|---|---| | `Source/LifePlay/LPCameraModeSubsystem.cpp` | 4 hunks, +156 / −1 (première version) | H1 `@@ -93` : constantes et noms dans `LPCameraModeLocal` ; H2 `@@ -133` : `ReadInt`, `ReadActors`, `AxisValue` dans l'espace nommé ; H3 `@@ -247` : `bMenuOnScreen = bMenu;` dans `Tick` (`:322`) ; H4 `@@ -401` : `TickSexCamera(DeltaTime);` à la place de `(void)DeltaTime;` dans `TickSwitchKey` (`:477`), puis le corps de `TickSexCamera` (`:481-561`) | | `Source/LifePlay/LPCameraModeSubsystem.h` | 1 hunk, +7 / 0 | `TickSexCamera`, `bMenuOnScreen`, `SexKeysCooldown` et leurs commentaires (`.h:70-76`) | - Tout est dans le périmètre de l'IMPL. **Aucun ajout non demandé.** Le code B2r (C1, C2, occlusion, CharGen, `LogSwitchKey`, `ArrayContains`, `MenuOnScreen`) n'a aucune ligne modifiée : la seule ligne `-` du diff est `(void)DeltaTime;`. - Les deux fichiers sont **non suivis par git** (`git status --short` : `??`). Un `git diff` contre un tag n'est donc pas possible : la référence est l'instantané. Le gardien devra les ajouter (`A`). - Non fait ici (hors périmètre VAL statique) : `Build.ps1`, `LPTestStringCompat`, ouverture de l'éditeur, essais en jeu. ## 5. Verdict **PASS-STATIC**, première passe puis reprise. Le code correspond à l'IMPL et à la SPEC, aucune erreur de compilation n'est attendue à la lecture, aucune dérive hors périmètre, B2r inchangé. Il ne vaut pas PASS : rien n'a été compilé ni exécuté. ## 6. Points à confirmer par l'utilisateur - [ ] V7-1 : en Scene (une fois B2p-R posé), D déplace la caméra vers sa droite autour du couple ; Q vers sa gauche. Le sens ressenti convient-il ? Sinon, inverser le signe de `Angle` (`.cpp:517`, `:520` dans la version actuelle). - [ ] V7-2 : en POV, D tourne le regard vers la droite, Z lève le regard. Le sens convient-il ? - [ ] V7-3 : Z/S en Scene : la caméra se rapproche ou s'éloigne sans saut brusque au premier appui (Constats 1). - [ ] V7-4 : après une scène, ZQSD ne fait rien hors sexe (la marche reprend), et rien pendant un menu. - [ ] V7-5 : le journal contient `Sexe : touches …` au plus une fois par seconde ; fournir ces lignes pour le KO Maj + souris. - [ ] BUILD : compilation de `LifePlayEditor` sous jeton UE (0 erreur ; relever tout nouvel avertissement sur `LPCameraModeSubsystem.cpp`). - [ ] Décision de l'utilisateur : P3 bis (molette) reste désactivé tant que l'essai de P1 n'a pas montré que le zoom d'origine (nœud 125) ne convient pas. Tours : 16 / 40 ## Constats Aucun défaut bloquant. Défauts non bloquants (première passe) : 1. **Saut de distance au premier appui** (`.cpp:524-525`). Si la caméra posée par le jeu est à plus de 800 cm (ou à moins de 80 cm) du pivot, le premier appui sur Z ou S ramène la distance à la borne en une seule image (saut visible). Conforme à la SPEC (bornes pendant l'avance et le recul) ; un correctif possible serait de n'appliquer la borne que dans le sens qui rapproche de l'intervalle. Laissé en l'état par l'IMPL : décision de l'orchestrateur. **Reste ouvert.** 2. **`DeltaTime` non borné** (`.cpp:477`, `:517`, `:524`). Après un à-coup d'une seconde, l'orbite fait 90° d'un coup et l'avance 200 cm. **Levé à la reprise** (voir Reprise). 3. **Écarts SPEC/IMPL non déclarés** : format du témoin, angles d'Euler monde au lieu de `AddWorldRotation`. **Levé : déclarés dans l'IMPL** (voir Reprise). 4. **Libellé ambigu** de l'IMPL (« D tourne vers la droite »). **Levé : précisé dans l'IMPL** (voir Reprise). 5. **Gabarit IMPL incomplet** : les sections « Points à confirmer en VAL » et « Fichiers à commiter » manquent. Fichiers à commiter attendus : `A Source/LifePlay/LPCameraModeSubsystem.cpp` et `A Source/LifePlay/LPCameraModeSubsystem.h` (non suivis dans git, § 4), plus l'IMPL et la présente VAL. **Reste ouvert** (je n'ai pas revu cette section à la reprise : elle n'apparaît pas dans les lignes 44-65 relues). 6. **Incertain, non mesuré** : un avertissement MSVC sur `bSexWheelZoom ? … : 0.f` (condition constante) n'est pas exclu à la compilation ; C4127 et C4702 sont coupés par la chaîne d'outils d'après `VCToolChain.cs`, mais seul `Build.ps1` le prouve. Si la molette est un jour activée, le sens de `ScrollDownUp` (+1 sur `MouseWheelAxis`, `DefaultInput.ini:154`) et l'échelle par cran seront à régler à l'essai. **Reste ouvert.** ## Reprise (2026-10-14) : borne du pas de temps (constat 2) Objet : revérifier la seule modification faite après la VAL. Toujours statique : ni compilation, ni éditeur, ni MCP. | Contrôle | Commande / lecture | Résultat brut | Verdict | |---|---|---|---| | MD5 | `md5sum` sur `Source/LifePlay/LPCameraModeSubsystem.cpp` et `.h` | `.cpp` `8e35e07ae08f3f208a24dc4cf2b3338f` (annoncé `8E35E07AE08F3F208A24DC4CF2B3338F`) ; `.h` `9e20981f8c768371a0935e2c7ce9e134` (annoncé `9E20981F…`, inchangé) | PASS | | Lignes | `wc -l` | `.cpp` 820 (annoncé 820), `.h` 84 | PASS | | Fins de ligne | `tr -cd '\r' < fichier \| wc -c` | `.cpp` CR=0, `.h` CR=0 | PASS | | Diff contre l'instantané | `diff -u` instantané → actuel : 4 hunks, 153 lignes `+` non vides, 1 ligne `-` (`(void)DeltaTime;`). Comparaison avec mon diff de la première passe (`diff` des deux diffs, en-têtes et numéros de hunk mis à part) | Les seules différences avec la première passe sont : (1) `+ const float SexMaxStep = 0.05f; // pas de temps maximal pris en compte (s)` (`.cpp:117`) ; (2) deux lignes dans `TickSexCamera` : un commentaire et `const float Step = FMath::Min(DeltaTime, LPCameraModeLocal::SexMaxStep);` (`.cpp:486`) ; (3) quatre substitutions `DeltaTime` → `Step` : `Angle` (orbite), `Distance - Forward * SexDollySpeed * Step` (avance ou recul), `Rotation.Yaw += Right * SexLookSpeed * Step` (lacet POV), `Rotation.Pitch + Forward * SexLookSpeed * Step` (tangage POV). Les numéros de hunk bougent de 1 à 3 lignes, rien d'autre | PASS : exactement 3 lignes ajoutées et 4 substitutions, aucune autre modification | | `DeltaTime` restant | `grep -n "DeltaTime\|Step"` sur la zone | dans `TickSexCamera`, `DeltaTime` n'apparaît plus qu'à la signature, au calcul de `Step` et à `SexKeysCooldown = FMath::Max(0.f, SexKeysCooldown - DeltaTime)` : le témoin garde le temps réel (une ligne par seconde réelle) | PASS | | Compilation à l'œil | lecture des lignes ajoutées | `SexMaxStep` : `const float` dans l'espace nommé `LPCameraModeLocal`, appelée qualifiée. `FMath::Min(float, float)` donne un `float`, `Step` est un `float` ; il remplace un `float` : mêmes types dans les produits `float`, puis `double` dans le `Clamp` comme avant. `Step` est déclaré avant son premier usage (`:486`, usages plus bas) ; aucun membre ni variable locale homonyme (`SexWheelStep` est un autre nom). Aucun nouvel include nécessaire (`FMath` déjà utilisée) | PASS-STATIC | | Effet voulu | lecture | Un à-coup d'image est borné à 0,05 s : au plus 4,5° d'orbite, 10 cm d'avance, 4,5° de regard par image. Au-dessus de 20 images par seconde, aucun changement de comportement | PASS-STATIC | | IMPL relu | `CAM-B2p-C-IMPL.md`, lignes 12-13 et 46-63 | Tableau : `.cpp` `8E35E07AE08F3F208A24DC4CF2B3338F`, 820 lignes ; `.h` inchangé. § Écarts : les constats 3 et 4 sont **déclarés** (format du témoin `avant/cote` et `couple N a` ; angles monde en POV au lieu d'`AddWorldRotation` ; sens de D, caméra vers sa droite en Scene et regard vers la droite en POV, ressenti à confirmer en V7). La reprise (constat 2) et le constat 1 laissé en l'état y sont notés | PASS | Constats après la reprise : 2 levé (borne ajoutée et revérifiée), 3 et 4 levés (déclarés dans l'IMPL). Restent ouverts, non bloquants : 1 (saut à la borne), 5 (sections « Points à confirmer en VAL » et « Fichiers à commiter » : non revues), 6 (avertissement MSVC possible). Un nouveau point, mineur : avec la borne à 0,05 s, sous 20 images par seconde la caméra va plus lentement que les vitesses annoncées (comportement voulu pour un jeu qui rame). **Verdict final de la reprise : PASS-STATIC.** Défauts bloquants : 0. MD5 final : `.cpp` `8e35e07ae08f3f208a24dc4cf2b3338f`, `.h` `9e20981f8c768371a0935e2c7ce9e134`. Tours (reprise comprise) : 16 / 40. Fichier écrit : `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\CAM\CAM-B2p-C-VAL.md` (à commiter : `A`).