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

182 lines
33 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-G-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-G-VAL : validation statique de la partie C++ (recadrage, suivi du couple, C3, témoin G3) (2026-10-14)
Portée : VAL **statique** (aucune compilation, aucun éditeur, aucun commandlet, aucun MCP, aucune écriture git).
Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8`, UE 5.8 (BuildSettingsVersion V7 : `/we4456 /we4458 /we4459`).
Les fichiers `Source/LifePlay/LPCameraModeSubsystem.cpp` et `.h` sont **non suivis** par git (`git status --short` : `??`) : il n'existe donc
pas de tag `CAM-B2p-G-avant` ; le diff est pris contre l'instantané `D:\Game\LifePlay_Backup_5.5\CAM-B2p-G\` par `git diff --no-index`.
**Deux passes** : la VAL initiale (sections 1 à 6, `.cpp` `6D88EDDA…`) et la **Reprise** (section finale, `.cpp` `A5F041FF…`) qui revérifie la correction de N1 et N2. Le verdict final est celui de la Reprise.
## 1. Fiches amont relues
- `Docs/Migration/etat/CAM/CAM-B2p-G-SPEC.md` (92 lignes, lue en entier).
- `Docs/Migration/etat/CAM/CAM-B2p-G-IMPL.md` (72 lignes, lue en entier ; seule IMPL du lot, partie C++). La correction de N1 et N2 n'a pas de livrable IMPL propre : elle est vérifiée dans le fichier source (Reprise).
- Amont de la base : `Docs/Migration/etat/ST/STG/STG-COMMUN11-BUILD.md` (lignes 21, 30, 134, 163-164 : le build COMMUN-11 a compilé
`LPCameraModeSubsystem.cpp` `03D85D28…` sans avertissement et contient la chaîne `Show couple : place reprise`).
- Lu en lecture seule : `Source/LifePlay/LPShowCoupleSubsystem.cpp` (l. 285-393, 750-824), `LPShowCoupleSubsystem.h`.
- Gabarit VAL lu : `D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md`.
## 2. Points à confirmer des IMPL (IMPL § 4, un par ligne)
| # | Point de l'IMPL | Verdict | Preuve |
|---|---|---|---|
| P1 | Lignes « vue recadree » à l'entrée en Scene et en C3 | PASS-STATIC (code) ; en jeu : reporté V7 | `.cpp` l. 841-850 (deux appels), ligne de journal en fin de `RecenterSceneCamera` ; format 6 `%` pour 6 arguments |
| P2 | Flèches et molette : la caméra suit le couple | PASS-STATIC (code) ; en jeu : reporté V7 (CAM-79) | `.cpp` l. 851-858 (avant reprise) ; appel avant la mise à jour de `LastCouplePivot` (l. 766-767, 828-829) |
| P3 | Distance de 900 cm au plus | PASS-STATIC (code) ; en jeu : reporté V7 | `.cpp` constante `FollowMaxDistance`, bloc de suivi |
| P4 | Ligne « G3 NotifyCoupleTeleported appele » à chaque pose qui déplace le meneur | PASS-STATIC (code) ; en jeu : reporté V7 | bloc « relâcher » de `TickCoupleMouse` ; une ligne par appel, condition `> 1.0` inchangée |
| P5 | Compilation | **BLOCKED-compilation** (VAL statique : aucune compilation autorisée). Relecture C++ UE 5.8 sans défaut trouvé (§ 3, contrôles 3 et 4, puis Reprise) | à confier à BUILD |
| P6 | Essais CAM-76 à CAM-80 | reporté (V7, en jeu) | aucune exécution possible ici |
## 3. Critères rejoués (commande → résultat) — VAL initiale
| # | Contrôle | Commande / lecture | Résultat brut | Verdict |
|---|---|---|---|---|
| 1a | MD5 | `md5sum` des quatre fichiers | `.cpp` `6d88edda0033e12d6891821d47f1267f` ; `.h` `00d439937dbceab44f459c4a834f34f0` ; instantané `.cpp` `03d85d282a6306e6d2494d7dc2c253fc`, `.h` `4ae6599447750371cff7e0aac74eb93e` | PASS : égalent l'IMPL (état d'avant la Reprise) |
| 1b | Lignes | `wc -l` | `.cpp` 1570 (instantané 1437) ; `.h` 141 (instantané 132) | PASS (1437+133 = 1570 ; 132+9 = 141) |
| 1c | CR | `tr -cd '\r' < f \| wc -c` (sans `grep -c`) sur les quatre fichiers | 0, 0, 0, 0 ; premiers octets `2f 2f 20` (pas de BOM), dernier octet `0a` | PASS : LF, sans BOM, fin de ligne finale |
| 2a | Diff compte | `git diff --no-index --numstat` | `.cpp` : `136 3` ; `.h` : `9 0` | PASS : 136 = 10 (constantes) + 11 (fin de sexe) + 1 + 1 (deux appels) + 100 (deux fonctions et ligne vide) + 8 (témoin G3) + 5 (étiquette) ; 3 retraits = 2 lignes `if` de G3 + 1 ligne `return` de l'étiquette |
| 2b | Périmètre | diff lu en entier (6 hunks `.cpp`, 1 hunk `.h`) | uniquement : constantes B2p-G ; `TickCoupleBounds` (remise à zéro hors sexe, deux appels) ; `TickSceneCamera` et `RecenterSceneCamera` ; bloc « relâcher » de `TickCoupleMouse` ; `ComputeLabel` ; membres du `.h` | PASS : aucun ajout non demandé |
| 2c | `const AActor* View` existants | diff + `Grep` des déclarations de `View` | déclarations aux l. 611, 1317, 1458 (`const AActor*`) et 989 (`AActor*`) : aucune ligne de diff n'y touche ; les nouvelles `AActor* View` (l. 837) sont dans `TickSceneCamera` seulement | PASS |
| 3 | Relecture C++ UE 5.8 | `Grep` dans `D:\Game\Launcher\Engine\UE_5.8\Engine\Source` (détail ci-dessous) | aucune erreur de type, de const ni de format trouvée | PASS-STATIC |
| 4 | Ombrage C4456/C4457/C4458/C4459 | lecture de chaque portée + `Grep` des membres et des noms globaux (détail ci-dessous) | aucun ombrage trouvé | PASS-STATIC |
| 5 | Logique (a) à (g) | lecture (détail ci-dessous) | conforme, trois réserves non bloquantes (N1 à N3) | PASS-STATIC |
| 6 | G3 | lecture de `LPShowCoupleSubsystem.cpp` | voir § « G3 » ci-dessous | PASS-STATIC |
| 7 | Cohérence SPEC/IMPL | lecture croisée | voir § « SPEC / IMPL » ci-dessous | PASS avec écarts acceptables |
### Contrôle 3, API UE 5.8 (sources lues dans `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime`)
- `AActor::AddActorWorldOffset(FVector, bool=false, FHitResult*=nullptr, ETeleportType=None)` : `Engine/Classes/GameFramework/Actor.h:1756`. Appel conforme.
- `SetActorLocationAndRotation(FVector, FRotator, …)` et surcharge `FQuat` : `Actor.h:1701-1702` ; `(Target - Desired).Rotation()` rend `FRotator` (`Core/Public/Math/Vector.h:832`) : pas d'ambiguïté. `SetActorLocation(const FVector&, …)` : `Actor.h:1636`.
- `FVector::Normalize(T Tolerance=UE_SMALL_NUMBER)` rend `bool` : `Vector.h:630`. `FVector::ForwardVector` déjà utilisé dans le module (`LPSecondScreenSubsystem.cpp:101`).
- `FVector(0.0, 0.0, float)` : constructeur non gabarit `TVector(T InX, T InY, T InZ)` (`Vector.h:168`), conversion `float` vers `double` implicite. `Flat * float` : `operator*(FArg)` arithmétique (`Vector.h:311-312`) ; `-Couple[0]->GetActorForwardVector()` : moins unaire sur le résultat de l'appel.
- `TWeakObjectPtr<AActor>` : `LastSexView = View` (AActor*) même forme que `GrabCamera = View` (déjà compilé) ; `LastSexView.Get() != SceneCamera` compare `AActor*` à `AActor*` ; `View == SceneCamera` : deux `AActor*` non const. Dans `ComputeLabel` les comparaisons existantes sur `const` restent inchangées.
- `FCollisionQueryParams(FName, bool, const AActor*)`, `SCENE_QUERY_STAT` (nom unique `LPRecenter`, une seule occurrence dans `Source`), `AddIgnoredActors(const TArray<AActor*>&)`, `AddIgnoredActor(const AActor*)` : `Engine/Public/CollisionQueryParams.h:39, 231, 243, 257`. Même forme que le code déjà compilé de `TickCoupleMouse`. `Cast<AActor>(ReadObject(...))` sans déréférence nulle.
- `LineTraceSingleByChannel` sur `const UWorld*` : même usage que dans `TickCoupleMouse` (`World` const, déjà compilé). `FHitResult()` appelle `Init()` qui fait `Memzero` (`Engine/Classes/Engine/HitResult.h:148-176`) : `Hit.bBlockingHit` vaut faux après une trace sans impact.
- `FMath::Clamp` : tous les arguments sont `double` (`Desired.X/Y/Z` double, `Area.Min.X + Margin` double, `Interior.FloorTop + 30.0` double, `FMath::Max(double, double)`). `FMath::Min(LowestRoot, cond ? double : double)`. Bornes du `Clamp` Z : minimum `FloorTop+30`, maximum `Max(FloorTop+30, Ceiling-20)` donc minimum inférieur ou égal au maximum, y compris plafond bas.
- Formats `UE_LOG` :
- recadrage : `%s`, `%.0f`, `%.0f`, `%.0f`, `%.0f`, `%s` = 6 ; arguments `Reason`, `FVector::Dist` (double), `Target.X/Y/Z` (double), ternaire `TEXT(…) : TEXT("")` = 6. `%s` reçoit un `const TCHAR*` (les deux littéraux de tailles différentes se réduisent en pointeur dans le ternaire, usage courant) ; `Hit.bBlockingHit` (champ de bits) en condition : valide.
- G3 : `%s`, `%.0f` = 2 ; arguments : ternaire de `TEXT` et `LeadMoved` (double) = 2. Condition du ternaire : pointeur `ShowCouple`, conversion contextuelle en `bool`.
- Const-correctness : `ReadBool(const UObject*, FName)` reçoit `const APlayerController* PC` et `APlayerController* EndPC` ; `WriteBool(UObject*, …)` reçoit `EndPC` non const. `ReadObject(const UObject*, …)` reçoit `AActor* Room` et `UGameInstance* GI`. `ComputeRoomInterior(const AActor*, …)` reçoit `AActor* Room`. `GetFirstPlayerController()` et `GetViewTarget()` appelables sur `const` (mêmes formes que dans le code déjà compilé).
- Qualification : tous les appels aux fonctions et constantes de l'espace de noms sont préfixés `LPCameraModeLocal::` (relu ligne à ligne) ; `PCSexC3`, `RecenterDistance`, `RecenterHeight`, `RecenterTargetAboveRoot`, `RecenterWallMargin`, `FollowMaxDistance`, `FollowMinStep` définies dans l'espace de noms.
- Signatures : `.h` l. 127 et 129 identiques aux définitions `.cpp`. Aucun paramètre ni variable inutilisé. `GI` n'est jamais nul dans `TickSceneCamera` (retour anticipé si `!GI`).
### Contrôle 4, ombrage de tout le fichier
Méthode : lecture de chaque fonction touchée, plus `Grep` (a) des membres du `.h` (l. 49-140), (b) des déclarations globales et des espaces de noms anonymes de `Source/LifePlay` (espaces de noms nommés ou anonymes dans 25 fichiers ; aucune déclaration de portée espace de noms nommée comme un des nouveaux noms ; les occurrences de `Desired`, `Hit`, `Query`, `World`, `PC`, `View`, `Mesh`, `Character`, `Person`, `Reason` hors module sont des locales de fonction ou un membre de struct : `LPBonusRewardSubsystem.h:79`, `LPSaveRelaunch.cpp:369`, etc.).
| Nouveau nom | Portée | Conflit |
|---|---|---|
| `EndWorld`, `EndPC` | bloc de retour anticipé de `TickCoupleBounds` | aucun : pas d'homonyme dans la fonction (`World` et `PC` des l. 748-749 sont dans un bloc `if` sans lien) |
| `World`, `PC`, `View`, `SceneCamera`, `bC3`, `bOnScene`, `Moved`, `Away` (`TickSceneCamera`) | corps de fonction ; `Moved` et `Away` dans le bloc `else if` | paramètres `GI`, `Room`, `Couple`, `Pivot`, `bGrabEnded` : aucun homonyme (C4457 exclu) ; membres `LastSexView`, `bSexC3Was`, `LastCouplePivot`, `bHasLastPivot`, `CoupleGrabMode` : aucun homonyme local (C4458 exclu) |
| `World`, `LowestRoot`, `Person`, `Character`, `Mesh`, `Target`, `Flat`, `Desired`, `Interior`, `Area`, `Margin`, `Query`, `Wall`, `CamRoom`, `Hit` (`RecenterSceneCamera`) | `Person`, `Character`, `Mesh` dans la boucle `for` ; `Area` et `Margin` dans le bloc `if` ; `Wall`, `CamRoom` en initialisation de `if` | paramètres `GI`, `Room`, `SceneCamera`, `Couple`, `Pivot`, `Reason` : aucun homonyme (C4457 exclu) ; aucun nom répété dans une portée englobante (C4456 exclu) ; aucun membre homonyme (C4458 exclu) |
| `LeadMoved`, `ShowCouple` (`TickCoupleMouse`) | bloc `!bHeld`, bloc `if (LeadMoved > 1.0)` | `ShowCouple` existait déjà en initialisation de `if` (baseline) : aucun autre `ShowCouple` ni `LeadMoved` dans la fonction (paramètres `GI`, `PC`, `View`, `Room`, `Couple`, `Pivot`) |
| C4459 (globaux visibles en unité unifiée) | — | aucun nom nouveau de portée globale ; les constantes ont le préfixe d'espace de noms nommé ; seul global proche : `CVarShowCouple` (`LPShowCoupleSubsystem.cpp:28`), nom différent de `ShowCouple` |
La baseline `03D85D28…` a été compilée sans avertissement (`STG-COMMUN11-BUILD.md:30`) ; les noms déjà présents (`Margin`, `Area`, `Interior`, `LowestRoot`, `Moved`, `Query`, `Hit`, `Wall`, `CamRoom`, `Target`, `SceneCamera`, `bC3`, `World`, `PC`, `View`) restent sans conflit global ; seuls `EndWorld`, `EndPC`, `bOnScene`, `Away`, `Flat`, `Desired`, `LeadMoved`, `Reason` étaient nouveaux et ne figurent dans aucune portée globale (`Grep` ci-dessus). `Axis` et `Reach`, ajoutés à la Reprise, sont traités dans la Reprise.
### Contrôle 5, logique
- (a) G-b : `LastSexView` remis à zéro hors sexe ; en sexe, `bOnScene && LastSexView.Get() != SceneCamera` déclenche un recadrage une fois, puis `LastSexView = View` ; plus de recadrage tant que la vue reste `SceneCamera`. Retour depuis le dernier POV : la vue change donc nouvelle entrée, nouveau recadrage. PASS-STATIC.
- (b) G-d : `else if (bOnScene && bC3 && !bSexC3Was)` ; `bSexC3Was = bC3` chaque image. Sans variable `SexC3` dans le PC, `ReadBool` rend faux (`FindPropertyByName` nul) : `bC3` faux, rien ne change ; l'étiquette reste « Camera: Scene ». Priorité du POV conservée dans `ComputeLabel`. PASS-STATIC.
- (c) G-c : condition `bOnScene && bHasLastPivot && CoupleGrabMode == 0 && !bGrabEnded` ; vecteur `Pivot - LastCouplePivot` ; appel avant `LastCouplePivot = Pivot` (pièce cachée et chemin normal) ; `Pivot` déjà corrigé par E1 (`Pivot += Correction` précède l'appel du chemin normal). La distance de 900 cm est évaluée à chaque image du bloc. Enchaînement exclusif en `else if` : une image recadrée ne suit pas. L'ordre de tick est établi : `TickSwitchKey` (intervalle 0) appelle `TickSexCamera` (qui contient `TickCoupleMouse`, donc `bGrabJustEnded` et `CoupleGrabMode` de la même image) puis `TickCoupleBounds`. PASS-STATIC.
- (d) `RecenterSceneCamera` : garde `!World || Couple.Num() == 0` ; bornes F1 seulement si `!Room->IsHidden() && ComputeRoomInterior` ; bornes XY seulement si la pièce fait plus de deux marges ; clamp Z toujours cohérent ; tracé Visibility cible vers caméra avec couple, `SceneCamera`, `Wall` et `CamRoom` ignorés ; rapprochement devant l'obstacle (version corrigée : Reprise) ; rotation vers la cible. PASS-STATIC.
- (e) Hors sexe : `PC.SexC3` remis à faux s'il était vrai ; `bSexC3Was` et `LastSexView` remis à zéro. `WriteBool` ne fait rien si la propriété est absente. PASS-STATIC.
- (f) Témoin G3 : `LeadMoved` calculé une fois, condition `> 1.0` identique à l'ancienne ; une ligne par appel avec le texte `appele` ou `non appele, sous-systeme absent` ; l'appel `NotifyCoupleTeleported` reste conditionné par `ShowCouple` non nul. PASS-STATIC.
- (g) Déréférences : `GI` non nul ; `Room` valide ; `PC` testé avant `GetViewTarget` ; `SceneCamera` utilisé seulement si `bOnScene` (donc valide et non nul) ; `Couple[0]` après `Couple.Num() == 0` ; `Mesh` testé avant usage ; `ReadBool(PC, …)` accepte `PC` nul. PASS-STATIC.
### G3 (contrôle 6)
- `KeepTogether` (`LPShowCoupleSubsystem.cpp:750-805`) écrit `LastSafe = Who->GetActorLocation();` en **l. 804**, à la fin de chaque passage qui va au bout. Deux retours anticipés existent : l. 756-758 (meneur, partenaire, volume ou monde absent) et l. 762-765 (`Delta.IsNearlyZero()`, auquel cas `LastSafe` égale déjà `Now` à la tolérance de `IsNearlyZero`). La formulation de l'IMPL « se termine à chaque image » est donc exacte à ce détail près ; le résultat est le même : `LastSafe` suit la place du meneur à chaque image où la scène est engagée. `KeepTogether` est appelée l. 388 (par `AfterActors`, branché sur `FWorldDelegates::OnWorldPostActorTick`, l. 275-285).
- `NotifyCoupleTeleported` (l. 807-824) : retour sans effet si `!bEngaged || !Who || !Partner || !GetWorld()` (l. 812-814) ; `Show couple : place reprise apres teleportation` n'est écrite (l. 819-820) **que si** `!(Now - LastSafe).IsNearlyZero()` (l. 817) ; l. 822-823 : `LastSafe = Now` et `LastBlocker.Reset()` toujours.
- Ordre dans l'image (établi, `Launch/Private/LaunchEngineLoop.cpp` 5.8) : `GEngine->Tick` (l. 5859) précède `FTSTicker::GetCoreTicker().Tick` (l. 6103). `OnWorldPostActorTick` (donc `KeepTogether`) s'exécute dans le tick du monde ; les tickers de `LPCameraModeSubsystem` (`FTSTicker`, l. 527-529) s'exécutent après. Au relâcher, le meneur n'a donc pas bougé depuis le dernier passage de `KeepTogether`, qui avait déjà remis `LastSafe` à sa place : `Now − LastSafe` est nul et **aucune ligne** n'est écrite.
- Conclusion sur COMMUN-11 : **l'absence de ligne « place reprise » au journal COMMUN-11 est attendue et ne prouve pas que G3 manque**. Le build COMMUN-11 contient G3 (établi : `STG-COMMUN11-BUILD.md:21` compile `LPShowCoupleSubsystem.cpp` et `LPCameraModeSubsystem.cpp` ; l. 134 la chaîne `Show couple : place reprise` passe de 0 à 1 ; l. 163 la CAM `03D85D28…`, état B2p-F + G3). Le critère d'essai de `COMMUN-11-TESTS.md:38` (« place reprise après chaque pose qui déplace le couple ») suppose à tort que la ligne paraît à chaque pose ; elle ne paraît que si `KeepTogether` a laissé `LastSafe` en arrière (pas bloqué ou ramené d'un pas). Le nouveau témoin (P4) tranchera en jeu que l'appel part. Niveau de preuve : ordre des tickers et résultat **établis par lecture** ; comportement observé en jeu **non rejoué**.
### SPEC / IMPL (contrôle 7)
- Conforme à la SPEC : G-a (cible, distance 300, hauteur 120, azimut gardé, F1 à 20 cm, plafond, tracé Visibility, regard, ligne de journal), G-b, G-c (même vecteur, figé pendant le glisser, 900 cm), G-d côté C++ (front montant, étiquette « Camera: C3 Couple », remise à faux hors sexe), G-e (Room non touché : aucun asset ni Blueprint dans le périmètre).
- Écarts acceptables, tous annoncés par l'IMPL : partie PC de G-d (variable `SexC3` et deux branches) laissée à MCP sous UE-GO ; le recadrage d'entrée en Scene remplace le cadrage `pickPreset` à chaque retour de POV (voulu, SPEC G-b) ; témoin G3 côté appelant (demande de Big Boss, hors SPEC mais cité et sans effet de comportement) ; la ligne de journal du recadrage est plus riche que la ligne minimale de la SPEC (ajout de distance, cible, obstacle : acceptable).
- Écart de gabarit (non bloquant) : voir Constats C1 et C2.
## 4. Diff lu en entier
| Fichier | Instantané → actuel | Hunks | Compte brut | Lecture |
|---|---|---|---|---|
| `Source/LifePlay/LPCameraModeSubsystem.cpp` | 1437 → 1570 lignes (avant Reprise) ; 1437 → 1575 (Reprise) | 6 : constantes ; fin de sexe ; deux appels ; `TickSceneCamera` et `RecenterSceneCamera` ; G3 ; `ComputeLabel` | `136 3` (avant Reprise) ; `141 3` (Reprise) | entier, deux fois |
| `Source/LifePlay/LPCameraModeSubsystem.h` | 132 → 141 lignes | 1 : deux déclarations et deux membres (l. 125-133) | `9 0` (inchangé) | entier |
Aucun autre fichier du dossier de l'instantané (`manifest.csv` plus les deux fichiers). `LPShowCoupleSubsystem.cpp/.h` (`M` dans `git status`, travail STE) : lus seulement, rien écrit par ce lot (non prouvable par instantané, qui ne les contient pas ; l'IMPL § 2 annonce « rien écrit »).
## 5. Verdict
**VAL initiale : PASS-STATIC**, aucun défaut bloquant. Ce verdict ne couvre ni la compilation (P5, BLOCKED-compilation, à confier à BUILD), ni le comportement en jeu (P1 à P4 et P6, reportés à V7). **Verdict final après la Reprise : PASS-STATIC** (N1 et N2 levés ; voir la section Reprise pour une réserve nouvelle, N7, non bloquante).
## 6. Points à confirmer par l'utilisateur (cases)
- [ ] BUILD : compilation de `LifePlay` (module touché doit figurer dans les unités compilées) avec 0 erreur et 0 avertissement nouveau sous V7 (P5).
- [x] N1 : levé à la Reprise (seuil 0,01 cm). Reste à confirmer en jeu que la caméra ne tremble pas.
- [x] N2 : levé à la Reprise (distance minimale de 50 cm). Compromis noté (N2b).
- [ ] N3 : accepter qu'un recadrage déclenché pendant une prise soit défait par la caméra figée, ou demander de le différer.
- [ ] N7 : décider du renommage de `Reach` (voir Reprise), ou laisser BUILD trancher.
- [ ] Décision sur le critère `COMMUN-11-TESTS.md:38` (« place reprise après chaque pose ») à reformuler à la lumière du G3 ci-dessus.
- [ ] Essais en jeu CAM-76 à CAM-80 et lignes « vue recadree » / « G3 NotifyCoupleTeleported » (V7), après la partie PC de G-d.
Tours : 29 / 40 (VAL initiale 22, Reprise 7)
## Constats
### Défauts bloquants
Aucun (preuves : contrôles 1 à 7 ci-dessus et Reprise).
### Défauts non bloquants
| # | Ligne (VAL initiale) | Constat | Niveau de preuve | État |
|---|---|---|---|---|
| N1 | `.cpp` 855 | Le suivi ignorait tout pas de pivot de 0,5 cm ou moins par image, et `LastCouplePivot` est réécrit à chaque image : un mouvement lent n'était jamais suivi. | estimé | **levé à la Reprise** |
| N2 | `.cpp` 926 | Si le tracé touchait à moins de 20 cm de la cible, la caméra passait de l'autre côté de la cible. | estimé | **levé à la Reprise** (N2b : compromis) |
| N3 | `.cpp` 844-850, 866 ; 1304-1306 | Un recadrage déclenché pendant une prise (touche C avec le bouton de souris tenu, ou entrée en Scene en pleine prise) est défait à l'image suivante par `TickCoupleMouse` qui remet la pose de prise (`GrabCameraLocation`) ; `bSexC3Was` est déjà à vrai, donc pas de nouvelle tentative, alors que la ligne « vue recadree » a été écrite. De même, un front montant de `SexC3` pendant lequel la vue n'est pas `SceneCamera` est perdu. | estimé (lecture, non rejoué) | ouvert |
| N4 | `.cpp` 719-726 | Hors sexe, la lecture par réflexion de `PC.SexC3` s'exécute à chaque image ; même ordre de coût que `ReadBool(GI, SexOn)`. Sans importance mesurable attendue. | estimé | ouvert, négligeable |
| N5 | `.cpp` 859-864 | La borne de 900 cm n'est évaluée que hors prise ; une prise suivie d'un relâcher peut laisser la caméra au-delà de 900 cm pendant la prise, puis la ramener d'un saut. La pièce fait 7,25 × 7 m (SPEC § 1) : cas pratiquement impossible. | estimé | ouvert, négligeable |
| N6 | `.cpp` 840-841 | Si le jeu change la vue avec un fondu (`SetViewTargetWithBlend` à durée non nulle), `GetViewTarget()` ne rend la nouvelle cible qu'en fin de fondu et le recadrage d'entrée se fait alors ; comportement de Room non relu. | incertain (essai : CAM-78) | ouvert |
| N7 | `.cpp` 930 (Reprise) | Nouveau nom local `Reach` homonyme de `const float Reach` dans l'espace de noms anonyme de `LPDirectControlSubsystem.cpp:58` : C4459 possible (erreur sous `/we4459`) **si** ce fichier précédait `LPCameraModeSubsystem.cpp` dans une unité unifiée. Voir Reprise. | estimé (ordre alphabétique constaté dans `Module.LifePlay.cpp`) | ouvert, non bloquant |
### Écarts de gabarit des livrables amont (écart = section absente)
- **C1, IMPL** : le gabarit IMPL exige la tête commune (`Fiche … — Niveau … — Modèle prévu … | utilisé … — Agent … — Skills …`), absente (la ligne 1 est le titre) ; les sections ne suivent pas l'ordre 1 Fichiers modifiés (chemins, lignes), 2 Écarts à la SPEC, 3 Instantané, 4 Ce qui n'a pas été fait, 5 Points à confirmer en VAL, 6 Fichiers à commiter : l'IMPL a « 1. Écritures », « 2. G3 », « 3. Écarts », « 4. Points à confirmer en V7 », « 5. Fichiers à commiter » ; l'instantané n'est pas une section (sans manifeste cité ; le dossier contient `manifest.csv`), « Ce qui n'a pas été fait » est fondu dans § 3 ; la ligne `Tours :` est absente ; le dépôt n'est pas précisé dans « Fichiers à commiter ». Non bloquant ; le contenu est exact (MD5, lignes, périmètre recomptés). La correction N1/N2 n'est pas consignée dans l'IMPL (MD5 annoncé de l'IMPL : `6D88EDDA…`, périmé) : à mettre à jour.
- **C2, SPEC** : le gabarit SPEC exige 1 Objectif mesurable, 2 Fichiers touchés et ordre, 3 Repli, 4 Risques R# et questions Q#, 5 Interdits, 6 Valeurs moteur lues (`chemin:ligne`), 7 Critères de VAL (commande → attendu), plus la tête commune. La SPEC a DIAG, Conception, Estimation, Risques : sans objectif mesurable, sans interdits, sans valeurs moteur, sans critères de VAL. Les critères de cette VAL viennent donc de la consigne de l'orchestrateur. Non bloquant.
- **C3, IMPL** : § 2 de l'IMPL écrit « KeepTogether … se termine à chaque image par LastSafe » : voir le détail des retours anticipés (G3 ci-dessus). Imprécision, sans conséquence sur la conclusion.
### Non mesuré
Compilation et avertissements ; exécution du moindre essai en jeu ; journal de la session COMMUN-11 relu (seul le rapport de build a été lu) ; vitesse des flèches et de la molette ; comportement du Blueprint Room au changement de vue (N6) ; vérification de l'affirmation du commentaire « l'animation ne bouge pas le pivot » (donc absence de tremblement avec le seuil de 0,01 cm) ; `LPTestStringCompat` et porte G-U3 (sans objet : aucun changement de chaîne, de configuration ni de compilation dans ce lot).
### Fichiers à commiter (VAL)
A `Docs/Migration/etat/CAM/CAM-B2p-G-VAL.md` (dépôt du jeu, branche `ue5.8`). Fichiers modifiés par la VAL : ce livrable seulement.
### À verser
`lp-validate` § 1 : un fichier source non suivi par git n'a pas de tag `<lot>-avant` ; comparer à l'instantané par `git diff --no-index --numstat` (code 1 = différences). `lp-build`/`lp-ue-compat` : ordre de tick établi en 5.8 — `GEngine->Tick` (LaunchEngineLoop.cpp:5859) avant `FTSTicker::GetCoreTicker().Tick` (l. 6103), donc `OnWorldPostActorTick` précède les tickers `FTSTicker` des sous-systèmes. `lp-validate` § 4 (C4459) : l'unité unifiée est lisible dans `Intermediate/Build/Win64/x64/UnrealEditor/Development/LifePlay/Module.LifePlay.cpp` (ordre alphabétique insensible à la casse des `#include` ; `LPCameraModeSubsystem.cpp` en est absent quand il est modifié, compilé seul `[NoUba]`) ; vérifier l'ordre avant de conclure sur un nom homonyme d'un espace de noms anonyme d'un autre fichier du module.
## Reprise (2026-10-14) : correction de N1 et N2
Statique, aucune écriture hors ce livrable. Entrée : message de l'orchestrateur (`.cpp` annoncé `A5F041FFA8562F9E75560EAD01725E2B`, 1575 lignes ; `.h` inchangé `00D43993…`).
### R1. Contrôles rejoués
| Contrôle | Commande | Résultat brut | Verdict |
|---|---|---|---|
| MD5 | `md5sum` | `.cpp` `a5f041ffa8562f9e75560ead01725e2b` ; `.h` `00d439937dbceab44f459c4a834f34f0` | PASS : égalent l'annonce |
| Lignes | `wc -l` | `.cpp` 1575 ; `.h` 141 | PASS (1570 + 5) |
| CR | `tr -cd '\r' < f \| wc -c` | 0 et 0 ; premiers octets `2f 2f 20`, derniers `7d 0a` | PASS : LF, sans BOM |
| Diff contre l'instantané | `git diff --no-index --numstat` puis diff `-U2` lu en entier | `.cpp` `141 3` (avant : `136 3`) ; `.h` `9 0` | PASS : +5 = +2 (ligne de constantes : commentaire et `RecenterMinDistance` ajoutés autour de `FollowMinStep`) +3 (bloc d'obstacle : 1 ligne remplacée par commentaire, `Axis`, `Reach`, `Desired`) ; les 3 retraits sont inchangés ; tous les autres hunks sont identiques à ceux de la VAL initiale |
| Périmètre | lecture | seules les constantes de suivi et le bloc `if (LineTraceSingleByChannel…)` de `RecenterSceneCamera` diffèrent de la VAL initiale | PASS : aucun ajout non demandé |
### R2. N1 : constante de suivi
- Lignes (diff) : `const float FollowMinStep = 0.01f;` précédée d'un commentaire (« Le pivot est la place des acteurs (l'animation ne la bouge pas) : tout pas compte, meme lent »), et `const float RecenterMinDistance = 50.f;` (cm). Utilisation inchangée : `Moved.Size() > LPCameraModeLocal::FollowMinStep` (`double` contre `float`, sans avertissement).
- Effet : le seuil par image tombe de 0,5 à 0,01 cm, soit 0,6 cm/s à 60 i/s : tout mouvement volontaire (flèches, molette, borne E1) est suivi, y compris à 240 i/s. **N1 levé** (PASS-STATIC).
- Réserve non mesurée : si un acteur du couple dérivait de plus de 0,01 cm par image sans intervention (bruit numérique, recalage du jeu), la caméra suivrait cette dérive ; c'est le comportement voulu par G-c (« le jeu »), mais l'absence de tremblement en jeu n'est pas rejouée. L'affirmation du commentaire sur l'animation n'est pas vérifiée ici.
### R3. N2 : bloc d'obstacle
Code lu (`.cpp` l. 926-932) :
```
if (World->LineTraceSingleByChannel(Hit, Target, Desired, ECC_Visibility, Query))
{
const FVector Axis = (Desired - Target).GetSafeNormal();
const double Reach = FMath::Max(FVector::Dist(Target, Hit.Location) - LPCameraModeLocal::RecenterWallMargin, static_cast<double>(LPCameraModeLocal::RecenterMinDistance));
Desired = Target + Axis * Reach;
}
```
- Types : `FVector::Dist` rend `T` = `double` (`Core/Public/Math/Vector.h:1015, 2464`) ; `Hit.Location` (`FVector_NetQuantize`, dérivée de `FVector`) se lie à `const TVector<double>&` par conversion vers la base (même emploi que `LPDirectControlSubsystem.cpp:1582`) ; `double - float` donne `double` ; `static_cast<double>(float)` : second argument `double` ; `FMath::Max(double, double)` : même type, même forme qu'à `.cpp` l. 909 (`FMath::Max(Interior.FloorTop + 30.0, Interior.Ceiling - Margin)`) et à `TickCoupleMouse` (`FMath::Max(OutZ, FloorBox.Max.Z)`) ; `Axis * Reach` : `FVector * double` (`operator*(FArg)` arithmétique, `Vector.h:311-312`) ; `Target + …` : `FVector` ; `Desired` non const. **Aucune erreur de type.** PASS-STATIC.
- Ordre d'évaluation : `Axis` est calculé avant l'affectation de `Desired` et ne dépend que de `Desired` d'avant ; la ligne remplacée de la VAL initiale n'existe plus (`Grep` : un seul `Desired =` dans le bloc de trace).
- Logique : le tracé est non dégénéré quand il touche (une trace de longueur nulle ne touche pas), donc `Desired - Target` n'est pas nul et `Axis` est un vecteur unitaire de la cible vers l'ancienne position visée : **la caméra reste du côté de la cible d'où vient `Desired`**. `Reach = Max(distance au point d'impact − 20 cm, 50 cm)` : **jamais moins de 50 cm** de la cible (établi par la forme de l'expression). La caméra ne peut plus passer de l'autre côté de la cible (défaut de N2). **N2 levé** (PASS-STATIC).
- N2b, compromis non bloquant : si l'impact est à moins de 50 cm de la cible, la caméra est posée à 50 cm, donc au-delà de l'obstacle, de 50 − d cm (d = distance cible-impact, d < 50). Pour d entre 50 et 70 cm elle reste devant l'obstacle (d − 20 < 50 ≤ d). Si la distance visée initiale est elle-même inférieure à 50 cm (pièce très étroite, bornes F1), `Reach` peut dépasser la distance visée. Les deux cas sont rares (obstacle collé au couple, hors couple et caméras) et l'exigence « au moins 50 cm » les accepte ; le journal l'écrit (`rapprochee devant un obstacle` si l'impact existe). Aucun test en jeu rejoué.
- Ligne de journal et rotation : `Hit.bBlockingHit` et `FVector::Dist(Desired, Target)` inchangés ; la distance écrite est désormais celle de la position finale.
### R4. Ombrage C4456 à C4459 pour `Axis` et `Reach`
Méthode : `Grep` de `\b(Axis|Reach)\b` dans `Source/LifePlay` (`*.cpp`, `*.h`), lecture des portées, lecture de l'unité unifiée générée.
- Portée : `Axis` et `Reach` sont déclarés dans le bloc `if` du tracé (l. 929-930) de `RecenterSceneCamera`. Paramètres de la fonction : `GI`, `Room`, `SceneCamera`, `Couple`, `Pivot`, `Reason` ; locales englobantes : `World`, `LowestRoot`, `Target`, `Flat`, `Desired`, `Interior`, `Query`, `Hit` : aucun `Axis` ni `Reach` : **C4456 et C4457 exclus**. Membres du `.h` (l. 49-140) : aucun `Axis` ni `Reach` : **C4458 exclu**.
- `Axis` : les autres occurrences sont `FName Axis` en **paramètre de fonction** de `LPCameraModeLocal::AxisValue` (`.cpp` l. 241, 251), `FName Axis` de `AxisValue` dans `LPDirectControlSubsystem.cpp:168, 178`, une boucle `int32 Axis` locale (`LPDirectControlSubsystem.cpp:1154`) et `FVector Axis` locale (`LPOutdoorRoomSubsystem.cpp:433-438`). Ce sont des paramètres ou des locales, pas des noms de portée globale ni d'espace de noms : **aucun C4459 pour `Axis`** (confirmé). La preuve par l'existant : `LPOutdoorRoomSubsystem.cpp:433` déclare `FVector Axis` en local dans la même unité unifiée sous V7 sans conflit, donc aucun `Axis` global n'existe dans les en-têtes de l'unité.
- `Reach` : **point d'attention.** `LPDirectControlSubsystem.cpp:58` déclare `const float Reach = 150.f;` dans un espace de noms **anonyme** (l. 53). La nouvelle locale `const double Reach` (l. 930) a le même nom. Établi : `Module.LifePlay.cpp` (unité unifiée, `Intermediate/Build/Win64/x64/UnrealEditor/Development/LifePlay/`) inclut les sources dans l'ordre alphabétique insensible à la casse (l. 31-57 : `LifePlay.cpp`, `LifePlayGameModeBase.cpp`, `LPAssetRegistryHelpers.cpp`, `LPColorGradeSubsystem.cpp`, … `LPDirectControlSubsystem.cpp` l. 37, … ) ; `LPCameraModeSubsystem.cpp` est absent de la copie générée (compilé seul, `[NoUba]`, comme dans `STG-COMMUN11-BUILD.md:21`), mais à l'ordre alphabétique il se placerait **avant** `LPDirectControlSubsystem.cpp`. Une déclaration locale ne peut masquer qu'un nom **déjà déclaré** à ce point de l'unité : ici l'anonyme `Reach` n'est pas encore vu, donc **pas de C4459 dans l'ordre actuel** (estimé : l'ordre alphabétique est constaté dans le fichier généré, pas garanti par le moteur ; la sensibilité exacte de C4459 aux noms d'un espace anonyme n'a pas été rejouée, faute de compilation). Une autre combinaison d'unités (ordre différent, fichier d'une unité à part qui inclurait `LPDirectControlSubsystem.cpp` avant, ou renommage de fichiers) pourrait en faire une erreur sous `/we4459`. **N7 (non bloquant).** Remède sans risque, à l'appréciation de l'orchestrateur : renommer la locale `Reach` en un nom unique (par exemple `ArmLength`) ; non fait par la VAL. Sinon BUILD le tranche (compilation de l'unité complète, pas seulement du fichier `[NoUba]`).
- Autres noms globaux possibles : `Reach` ne figure dans aucun autre fichier de `Source` ; pas de lecture des en-têtes moteur pour un `Reach` global (incertain, faible probabilité).
### R5. Verdict final et suite
- **PASS-STATIC** (N1 et N2 levés par lecture ; aucun défaut bloquant ; N7 non bloquant nouveau ; N3 reste ouvert comme avant). Non couverts : compilation (BLOCKED-compilation, BUILD), comportement en jeu (V7).
- Cases à jour : voir § 6. L'IMPL doit être mise à jour (MD5 `A5F041FF…`, 1575 lignes, correction N1/N2) ; la ligne « Fichiers à commiter » de l'IMPL ne change pas.