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

34 KiB
Raw Blame History

Fiche CAM-B2p-H-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-H-VAL : validation statique de G-e (pose sur le lit), G-f (surfaces admises), G-g (clic molette), maillage dans les refus, renommage de Reach (2026-10-14)

Portée : VAL statique (aucune compilation, aucun jeu, aucun éditeur, aucun commandlet, aucun MCP, aucune écriture git), par un validateur distinct de l'auteur. Copie D:\Game\LifePlay-master 5.8, branche ue5.8 (git branch --show-current : ue5.8), UE 5.8, BuildSettingsVersion V7 (/we4456 /we4458 /we4459). LPCameraModeSubsystem.cpp et .h sont non suivis par git (git status --short : ??), donc pas de tag CAM-B2p-H-avant : chaque point est jugé sur le diff de son instantané à l'instantané suivant (git diff --no-index), comme pour CAM-B2p-G-VAL.md. Convention des instantanés : CAM-B2p-Hn est l'état d'avant le point Hn ; le point Hn est donc le diff CAM-B2p-Hn vers CAM-B2p-H(n+1) (H5 : vers l'état final). Niveaux de preuve : établi = lecture ou commande rejouée aujourd'hui ; estimé = déduit ; incertain = non vérifié (essai nommé).

1. Fiches amont relues

  • Docs/Migration/etat/CAM/CAM-B2p-H-IMPL.md (100 lignes, lue en entier ; seule IMPL du lot).
  • Docs/Migration/etat/CAM/CAM-B2p-G-SPEC.md § 5 (l. 94-165 : DIAG 5.1, G-e 5.2, G-f 5.3, G-g 5.4, essais 5.5).
  • Docs/Migration/etat/CAM/CAM-B2p-G-VAL.md (183 lignes, lue en entier : N7 et forme d'une VAL statique C++ de ce lot).
  • Lu en lecture seule : Source/LifePlay/LPShowCoupleSubsystem.cpp l. 25-70 (IsBedMesh l. 38-62), Source/LifePlay/LPDirectControlSubsystem.cpp l. 48-62 (espace anonyme, Reach l. 58), Intermediate/Build/Win64/x64/*/*/LifePlay/Module.LifePlay.cpp.
  • Gabarit VAL lu : D:\Game\LifePlay-Sandbox\Tools\claude\skills\lp-doc\gabarits.md.
  • Écart de gabarit de l'IMPL (N5 ci-dessous) : l'IMPL n'a ni tête commune, ni section « Écarts à la SPEC », ni « Ce qui n'a pas été fait », ni « Points à confirmer en VAL », ni ligne Tours :. Les points à confirmer sont donc pris dans ses § 3 (risques R1 à R4) et § 4 (essais CAM-81 à CAM-85), plus la liste de la fiche.

2. Points à confirmer des IMPL (un par ligne)

# Point Verdict Preuve
P-H1 N7 : Reach renommé ObstacleReach, rien d'autre ; symbole STG confirmé ; aucun Reach local restant PASS-STATIC § 3 contrôles 5 à 7 ; .cpp l. 969-974 ; LPDirectControlSubsystem.cpp:58
P-H2 ComponentMesh, IsAdmittedBed, DescribeComponent ; règle des lits identique à IsBedMesh ; CoupleSpotFree même comportement, texte du bloqueur changé PASS-STATIC .cpp l. 478-500, 526, 530 ; LPShowCoupleSubsystem.cpp:56-59
P-H3 G-e : tracé ECC_Pawn, OutSurface nul pour le sol, E2 conservé, racine la plus basse à surface + 3 cm, ligne de prise ; écart SPEC (racine la plus basse) PASS-STATIC, écart jugé acceptable .cpp l. 133-134, 465-476, 1210-1241, 1296-1306, 1361-1369
P-H4 G-f : sol et lits seuls en pièce connue ; couple immobile sinon ; une ligne par changement ; remise à vide à la prise ; pièce cachée sans G-f PASS-STATIC .cpp l. 1288, 1364-1395 ; .h l. 121-122
P-H5 G-g : TickPanelClick pendant le sexe hors menu seulement ; hors sexe rien ne change ; 0,2 s / 5 px ; FindFunction + NumParms == 0 + ProcessEvent(…, nullptr) ; appui né dans un menu PASS-STATIC (réserve N1) .cpp l. 176-178, 987-992, 998-1003, 1027-1044, 1122-1155 ; .h l. 137-144
R1 (IMPL § 3) Le tracé Pawn peut toucher la tête de lit (même maillage) : couple posé dessus reporté V7 (CAM-81) géométrie des assets non lisible en statique ; code conforme à la règle de la SPEC (aucun plafond de hauteur, « non fait » annoncé)
R2 Capsules des PNJ sur le canal Pawn : place refusée PASS-STATIC une capsule est un UCapsuleComponent : ComponentMesh rend nul (l. 481-482), IsAdmittedBed faux (l. 489-491), refus l. 1376-1385
R3 Curseur sans position pendant le clic molette : seul le délai compte PASS-STATIC .cpp l. 1127, 1132, 1139-1140 : le mouvement n'est mesuré que si bMiddleHadMouse && bPanelMouse
R4 ProcessEvent de LeftMouseSexOn hors boucle du monde (FTSTicker) forme de l'appel PASS-STATIC ; contexte d'appel incertain : reporté V7 précédent d'appel identique LPDirectControlSubsystem.cpp:595 (Player->ProcessEvent(Function, nullptr)) ; contexte (tick du monde ou ticker) non relu ; essai : CAM-83
CAM-81 à CAM-84 Essais en jeu (lit, commode, clics, hors sexe) reporté V7 (build COMMUN-12) aucune exécution possible ici
CAM-85 Ligne place refusee avec maillage ; compilation sans C4456 / C4458 / C4459 ligne : PASS-STATIC (l. 530 puis 1399) ; compilation : BLOCKED-compilation (BUILD) § 3 contrôles 9 à 11

3. Critères rejoués (commande → résultat)

Les commandes PowerShell sont des fichiers de travail du dossier temporaire de session (scratchpad), pas des fichiers du dépôt.

# Contrôle Commande / lecture Résultat brut Verdict
1 MD5 final Get-FileHash -Algorithm MD5 .cpp 210B665AA8D5D628B8C1AC429A339B74 ; .h 239DA6094E7401E0E3B3699A635A8150 PASS : égalent la fiche et l'IMPL
2 Lignes, fins de ligne, BOM lecture des octets (comptage de 13 et de 10, sans grep -c) .cpp size 73384, CR=0, LF=1687, premiers octets 2f2f20, dernier 0a ; .h size 8471, CR=0, LF=152, 2f2f20, 0a PASS : 1687 et 152 lignes, LF pur, sans BOM, fin de ligne finale
3 MD5 et lignes des instantanés idem sur D:\Game\LifePlay_Backup_5.5\CAM-B2p-H1 à H5 H1 A5F041FF…/00D43993… (1575/141 lignes) ; H2 747FA007… (1576) ; H3 756C95F0… (1596) ; H4 91FD48F0… (1624), .h BBB45AD0… (142) ; H5 48F20ED7… (1637), .h 924B63F8… (144) ; CR=0 partout PASS : égalent le tableau de l'IMPL § 0 et chaque manifest.csv ; chaque dossier contient 3 fichiers (manifest.csv, .cpp, .h)
4 Diff brut et répartition git diff --no-index --numstat puis recompte par -U0 (lignes +/- hors en-têtes) voir tableau ci-dessous PASS : somme des points = global, à une ligne remplacée près, expliquée
5 Reach restant Grep \bReach\b sur Source/LifePlay (*.cpp, *.h) LPCameraModeSubsystem.cpp:970 (commentaire) ; LPDirectControlSubsystem.cpp:58, :1357, :1379 PASS : plus aucun Reach de code dans CameraMode (l. 970 est un commentaire)
6 Symbole STG Read LPDirectControlSubsystem.cpp l. 48-62 l. 53 namespace, l. 54 {, l. 58 const float Reach = 150.f; : espace de noms anonyme PASS : confirmé
7 Ordre des unités unifiées Grep de Module.LifePlay.cpp (UnrealEditor Development, UnrealGame Shipping) LPDirectControlSubsystem.cpp inclus (l. 37 / 36), LPShowCoupleSubsystem.cpp (l. 53 / 52) ; LPCameraModeSubsystem.cpp absent des unités générées (compilé seul quand il est modifié) PASS : ObstacleReach écarte tout C4459 quel que soit l'ordre des unités
8 Signatures et symboles Grep des 24 noms nouveaux sur Source/LifePlay ; Grep namespace LPCameraModeLocal sur Source TickPanelClick : .h:139 et .cpp:1122 (même signature), un seul appel .cpp:1043 ; namespace LPCameraModeLocal : un seul fichier PASS : pas de doublon de symbole externe
9 C4456 / C4457 (locaux englobants) lecture de TickCoupleMouse (l. 1166-1427), TickSexCamera (980-1121), TickPanelClick, CoupleLowestRoot, IsAdmittedBed, DescribeComponent détail ci-dessous PASS-STATIC
10 C4458 (membres de la classe) lecture du .h en entier (152 lignes) contre chaque nouveau local et paramètre détail ci-dessous PASS-STATIC
11 C4459 (globaux et espaces anonymes de tous les .cpp) Grep des noms nouveaux dans tout Source/LifePlay seuls hits hors CameraMode : Lowest (LPDebugSubsystem.cpp:508, local de fonction), Toggle (LPModInfoSubsystem.cpp:113, 288, locaux), Refused (LPHairstyleSubsystem.cpp:230, local) PASS-STATIC (résidu engine : N6)
12 Types, includes, API 5.8 lectures du moteur (détail ci-dessous) aucune erreur de type trouvée PASS-STATIC
13 Formats UE_LOG / Printf comptage % contre arguments (détail ci-dessous) tous cohérents PASS-STATIC
14 Non-régression B2p-C à B2p-G liste des hunks (§ 4) contre les fonctions des lots précédents aucun hunk dans TickCoupleBounds, TickSceneCamera, ComputeLabel, NotifyCoupleTeleported (G3 l. 1321-1335 hors diff) PASS-STATIC
15 Fichiers STG / STE git status --short et dates ; instantanés ne contenant que CameraMode LPDirectControlSubsystem.* modifiés 12:05:58, LPShowCoupleSubsystem.* 12:43:20 ; CameraMode 14:08:43 ; instantanés H1 à H5 de 14:06:24 à 14:08:05 PASS : aucune écriture STG/STE après le début de H (établi par les dates ; l'instantané ne les contient pas, donc pas de preuve par octets)

Contrôle 4 : bilan brut et répartition par point

Point Diff .cpp ajoutées / retirées (numstat et -U0 concordent) Lignes .cpp avant vers après (LF) .h
H1 H1 vers H2 +3 / −2 1575 vers 1576 (+1, ok) 0
H2 H2 vers H3 +26 / −6 1576 vers 1596 (+20, ok) 0
H3 H3 vers H4 +41 / −13 1596 vers 1624 (+28, ok) +2 / −1 (141 vers 142)
H4 H4 vers H5 +15 / −2 1624 vers 1637 (+13, ok) +2 / −0 (142 vers 144)
H5 H5 vers final +52 / −2 1637 vers 1687 (+50, ok) +8 / −0 (144 vers 152)
Somme +137 / −25 1575 vers 1687 (+112, ok) +12 / −1
Global H1 vers final +136 / −24 +112, ok +12 / −1
  • Écart de la somme au global : +1 / −1 sur le .cpp, expliqué : la ligne if (TraceDown(Target, SurfaceZ, Surface)) est ajoutée par H3 (en remplacement de if (bGrabHasHeight && TraceDown(Target, SurfaceZ))) puis retirée par H4 (remplacée par const bool bSurface = …; et if (bSurface)) : elle compte une fois en ajout et une fois en retrait dans la somme, et jamais dans le global. Net identique (+112). Le .h retombe exactement (12 / 1).
  • Retraits de H3 (13), recomptés par nature : tracé vertical et E2, 6 (2 lignes de commentaire E2, signature du lambda, ligne du canal ECC_Visibility, if (bOverFloor), OutZ = FMath::Max(…)) ; prise, 4 (bGrabHasHeight = TraceDown(Pivot, SurfaceZ);, GrabHeight = …, format du UE_LOG, ligne du ternaire Printf) ; glisser, 3 (commentaire, if (bGrabHasHeight && TraceDown(…)), Target.Z = SurfaceZ + GrabHeight;) : 6 + 4 + 3 = 13, conforme au diff lu. Les retraits de H2 (6) sont les 6 lignes de CoupleSpotFree déplacées dans ComponentMesh / IsAdmittedBed / DescribeComponent ou remplacées ; ceux de H4 (2) et de H5 (2) sont des lignes remplacées par une version plus longue (if de G1 devenu else if, commentaires).
  • Sorties collées (recompte -U0) : cpp H1 vers FINAL : +136 -24 et h H1 vers FINAL : +12 -1 ; numstat : 136 24 et 12 1.

Contrôles 9 à 11 : ombrage (détail)

Nouveaux noms et leur portée :

Nom Portée C4456/C4457 C4458 C4459
Lowest, Person, Character, Mesh CoupleLowestRoot (l. 466-476), Lowest hors boucle, les trois autres dans la boucle aucun homonyme dans la fonction (paramètre Couple seul) aucun membre (fonction de l'espace de noms) Lowest seulement local ailleurs (LPDebugSubsystem.cpp:508) ; Person, Character, Mesh existaient déjà comme locaux compilés (l. 1057, 1268-1269)
Comp, Mesh, MeshComp, MeshName ComponentMesh, IsAdmittedBed, DescribeComponent (l. 479-500) aucun homonyme (un Mesh par fonction) sans objet Comp, MeshComp, MeshName existaient (code compilé de CoupleSpotFree)
OutSurface paramètre du lambda TraceDown (l. 1218) aucun OutSurface englobant aucun membre aucun hit hors CameraMode
GrabSurface bloc de prise CoupleGrabMode == 0 (l. 1298) aucun homonyme (SurfaceZ voisin, nom distinct) aucun membre aucun hit
Surface, bSurface, Refused bloc du glisser (l. 1364-1380) locaux englobants relus (Input, bLeft…, World, Query, TraceCursor, FloorComponent, RoomActor, bFloorKnown, FloorBox, TraceDown, MouseX, MouseY, bMouse, bHeld, Aim, Target, Interior, Area, Margin, Blocker, LeadCharacter, LeadMesh, RootOffset) : aucun LastSurfaceRefused est le membre, Refused en est distinct Refused : local ailleurs (LPHairstyleSubsystem.cpp:230, indentation d'une tabulation = corps de fonction)
bMiddle, PanelMouseX, PanelMouseY, bPanelMouse, Toggle ; paramètres PC, DeltaTime TickPanelClick (l. 1122-1155) aucun doublon dans la fonction membres bMiddleWasDown, bMiddleHadMouse, bMiddleMoved, MiddleHeld, MiddleStart : noms différents des locaux ; aucun membre PC ni DeltaTime Toggle : locaux de fonction ailleurs (LPModInfoSubsystem.cpp:113, 288)
ObstacleReach RecenterSceneCamera (l. 972) aucun homonyme aucun membre aucun hit
  • Aucun nom de portée globale ou d'espace anonyme n'est ajouté : constantes et fonctions sont dans LPCameraModeLocal (espace nommé) ; les constantes const ont de plus un lien interne. Grep d'un Toggle / Surface / Lowest / Refused isolé sur une ligne dans Engine/Classes/*.h (5.8) : 0 résultat (limité à ce dossier, voir N6).

Contrôle 12 : types, includes, API (sources 5.8 lues)

  • UObject::FindFunction(FName) const : CoreUObject/Public/UObject/Object.h:1258 ; ProcessEvent(UFunction*, void*) : Object.h:1490 ; UFunction::NumParms (uint8) : Class.h:2574 ; précédents du projet : LPDirectControlSubsystem.cpp:582-595, LPDataCompat.cpp:149-164.
  • FHitResult::GetComponent() const rend UPrimitiveComponent* : Engine/Classes/Engine/HitResult.h:227 ; OutSurface = bHit ? Hit.GetComponent() : nullptr; (l. 1230) : UPrimitiveComponent* vers const UPrimitiveComponent*&, aucun problème. Le lambda reçoit exactement const UPrimitiveComponent* (GrabSurface l. 1298, Surface l. 1364) : la référence se lie.
  • APlayerController::GetMousePosition(float&, float&) const : PlayerController.h:1623 (surcharge double& à côté, pas d'ambiguïté avec des float). FVector2D::Distance, FVector2D(float, float) : même emploi qu'à .cpp l. 1289 et 1344. bMiddleMoved |= (bool && bool && bool) : même forme que bGrabMoved |= l. 1344.
  • Includes déjà présents (l. 5-28) : UObject/UnrealType.h (donc UFunction), GameFramework/PlayerInput.h (EKeys::MiddleMouseButton, comme LeftMouseButton déjà utilisé), Components/StaticMeshComponent.h, Engine/StaticMesh.h, GameFramework/Character.h, Components/SkeletalMeshComponent.h. Aucun include à ajouter. TNumericLimits<double>::Max() : CoreMinimal (même usage LPDebugSubsystem.cpp:508).
  • Ternaires : Mesh ? *Mesh->GetName() : TEXT("sans maillage statique") (l. 499) = const TCHAR* / tableau, se réduisent en const TCHAR* ; l. 1305-1306 : bGrabHasHeight ? *FString::Printf(…) : TEXT("? cm") puis !bGrabHasHeight ? TEXT("inconnue") : GrabSurface ? *DescribeComponent(GrabSurface) : TEXT("sol") : imbriqué à droite, chaque branche finale const TCHAR* ; les FString temporaires vivent jusqu'à la fin de l'expression complète (l'appel UE_LOG), comme le motif déjà compilé qu'ils remplacent. Comp ? Comp->GetOwner() : nullptr : AActor* vers const UObjectBaseUtility* de GetNameSafe (précédent l. 1259).
  • const UStaticMesh* ComponentMesh(…) : MeshComp ? MeshComp->GetStaticMesh() : nullptr : UStaticMesh* converti en const UStaticMesh*. Mesh->GetName() sur pointeur const : UObjectBaseUtility::GetName() const.
  • double/float : Target.Z = Pivot.Z + (SurfaceZ + CoupleRootAboveSurface − CoupleLowestRoot(Couple)) : double + (double + float − double) ; FMath::Min(Lowest, cond ? double : double) : deux double ; Distance(…) > PanelClickMaxMove : double contre float ; MiddleHeld <= PanelClickMaxHold : float contre float.
  • .h : void TickPanelClick(APlayerController* PC, float DeltaTime); (APlayerController déclaré en avant l. 18) ; FVector2D MiddleStart = FVector2D::ZeroVector; même forme que GrabMouseStart (l. 97). Zone private: (l. 33).
  • Non-ASCII : uniquement des commentaires (l. 494 neuve avec « », comme les l. 74, 146, 536, 1328 déjà compilées) ; tous les TEXT(...) neufs sont en ASCII.

Contrôle 13 : formats

  • Prise (l. 1303-1306) : %s, %.0f ×3, %s, %s = 6 ; arguments : ternaire deplacement/rotation, Pivot.X, .Y, .Z (double), ternaire Printf/? cm, ternaire surface = 6. Printf interne %+.0f avec GrabHeight (double) = 1/1.
  • DescribeComponent (l. 498-499) : %s/%s (%s) = 3 ; arguments : 2 *GetNameSafe(…) + ternaire = 3.
  • Refus de surface (l. 1384) : %s = 1, *Refused. Clic molette (l. 1146) : %.2f = 1, MiddleHeld (float promu en double). Absence de l'événement (l. 1151) : aucun argument, aucun %.

Point par point : lecture du code

H1 (N7), diff H1 vers H2, +3 / −2. Le diff complet est de 3 lignes ajoutées (1 commentaire // ObstacleReach, pas Reach : … et les 2 lignes remplacées) et 2 retirées : const double Reach = … devient const double ObstacleReach = … (même expression, mêmes arguments) et Desired = Target + Axis * Reach; devient … * ObstacleReach; (.cpp l. 970-973). Aucun autre hunk. Verdict PASS-STATIC (contrôles 5 à 7 : STG:58 est bien dans l'espace anonyme l. 53-54 ; plus aucun Reach de code dans CameraMode ; Axis n'a pas changé).

H2 (maillage dans les refus), diff H2 vers H3, +26 / −6.

  • ComponentMesh (l. 478-483), IsAdmittedBed (l. 485-492), DescribeComponent (l. 494-500) ajoutées avant CoupleSpotFree, dans LPCameraModeLocal.
  • Règle des lits identique à IsBedMesh (LPShowCoupleSubsystem.cpp:56-59) : Contains("bed", IgnoreCase), Contains("fnt_ext011"), Equals("fnt_new045"), Equals("fnt_new235") : mêmes quatre tests, mêmes littéraux, même ordre (.cpp l. 490-491). Mesh nul : IsBedMesh rend faux (:40-43), IsAdmittedBed aussi (Mesh && (...), MeshName vide). Seule différence : STE met le résultat en cache par FName (:44-60), sans effet sur le résultat. Équivalence : établie par lecture caractère par caractère.
  • CoupleSpotFree même comportement : le bloc d'origine (calcul de MeshComp, Mesh, MeshName puis les quatre tests) est remplacé par if (IsAdmittedBed(Comp)) continue; (l. 526-529) ; la condition, le continue et le return false sont inchangés ; seule la ligne OutBlocker = Comp->GetName(); devient OutBlocker = DescribeComponent(Comp); (l. 530) : le texte du bloqueur passe de Room_C_0/NODE_… à Propriétaire/composant (maillage). L'appelant compare Blocker != LastSpotBlocker (l. 1396) : même logique, la clé est plus fine (le maillage s'ajoute au nom). Verdict PASS-STATIC.

H3 (G-e), diff H3 vers H4 (cpp +41 / −13, h +2 / −1).

  • Canal : LineTraceSingleByChannel(…, ECC_Pawn, Query) (l. 1223) ; Query est la même requête que le tracé du curseur (couple, vue, Wall, CamRoom ignorés, l. 1186-1196) ; ECC_Pawn déjà employé l. 517.
  • OutSurface : bHit ? Hit.GetComponent() : nullptr (l. 1230), puis nul si E2 relève le point au sol (l. 1231-1235), puis nul si le composant est FloorComponent (l. 1236-1239) : nul pour le sol dans les deux cas (impact sous le dessus du sol, ou Room.Box).
  • E2 conservé : l'ancien OutZ = Max(OutZ, FloorBox.Max.Z) (si au-dessus du sol connu) équivaut à if (bOverFloor && OutZ <= FloorBox.Max.Z) OutZ = FloorBox.Max.Z : identique pour toute valeur de OutZ. Les retours false (aucun impact et hors du sol) sont inchangés (l. 1225-1228).
  • Hauteur : Target.Z = Pivot.Z + (SurfaceZ + 3 − CoupleLowestRoot(Couple)) (l. 1368). Pivot est la moyenne des positions des acteurs, recalculée à chaque image (l. 1056-1064) et le déplacement est une translation rigide Delta = Target − Pivot appliquée à chaque personne (l. 1390-1394) : la racine la plus basse du couple arrive donc exactement à SurfaceZ + 3 cm (CoupleRootAboveSurface, l. 133-134). CoupleLowestRoot (l. 465-476) : Z du composant de maillage du ACharacter, acteur à défaut ; Couple non vide dans le glisser (bScene exige Couple.Num() > 0, l. 1177) ; même mesure que RecenterSceneCamera / E3 (HighestRoot l. 840 en est le pendant). Le garde bGrabHasHeight && du glisser est retiré (l. 1365-1369) : sans impact ni sol, Target.Z reste Pivot.Z comme avant.
  • Ligne de prise (l. 1303-1306) : Sexe : couple pris (deplacement|rotation) a (x, y, z), racine a +N cm de la surface <sol | Propriétaire/composant (maillage) | inconnue> ; GrabHeight = CoupleLowestRoot − SurfaceZ (l. 1300) ; .h l. 91-92 : commentaire mis à jour (journal seulement).
  • Écart avec la SPEC jugé (§ 5.2 : « surface + écart racine-acteur + 3 cm » du meneur ; IMPL : racine la plus basse du couple). Lecture : pour la translation rigide d'un couple, les deux formulations donnent la même hauteur dès que la racine du meneur est la plus basse (cas du sol et de la plupart des poses). Si l'autre personne a la racine plus basse, la formulation de la SPEC la laisserait de ce côté sous la surface (couple enfoncé dans le matelas) alors que celle de l'IMPL la pose à surface + 3 cm ; si au contraire le meneur est plus bas, les deux coïncident. L'IMPL est donc au moins aussi sûre que la SPEC, cohérente avec E3 et le recadrage (mêmes racines), et l'écart est annoncé (IMPL § 1 H3). Acceptable (estimé, par lecture ; l'essai CAM-81 le confirmera sur le lit). Il est à porter à la connaissance de Big Boss, non à corriger.
  • Verdict PASS-STATIC.

H4 (G-f), diff H4 vers H5 (cpp +15 / −2, h +2 / −0).

  • Condition (l. 1376) : bFloorKnown && bSurface && Surface && !IsAdmittedBed(Surface). bFloorKnown = sol valide et Room non cachée (l. 1216) : « pièce connue » ; Surface est nul pour le sol (H3) : le sol passe ; un lit admis passe ; tout le reste (commode, bureau, armoire, capsule de PNJ) est refusé.
  • Le couple ne bouge pas : la branche G-f (l. 1377-1386) ne contient que le journal ; le déplacement (l. 1390-1394) est dans la branche else if (CoupleSpotFree(…)) qui n'est pas atteinte ; Blocker reste vide et else if (Blocker != LastSpotBlocker) (l. 1396) n'est pas atteint non plus (chaîne if / else if / else if, une seule branche s'exécute).
  • Une ligne par changement : if (Refused != LastSurfaceRefused) puis affectation et UE_LOG (l. 1381-1385) ; .h l. 121-122 (membre FString). Remis à vide à chaque prise : LastSurfaceRefused.Reset(); (l. 1288), juste après LastSpotBlocker.Reset(); (l. 1287), dans le bloc de sélection (CoupleGrabMode == 0).
  • Pièce cachée sans G-f : bFloorKnown faux (Room cachée ou sol invalide) : la condition est fausse, on passe à CoupleSpotFree avec Floor nul, comme avant ; G-e s'applique si le tracé touche (bSurface).
  • Sol sans impact au-dessus : Surface nul, G1 comme avant.
  • Verdict PASS-STATIC (réserves N2 et N3).

H5 (G-g), diff H5 vers final (cpp +52 / −2, h +8 / −0).

  • Appel seulement pendant le sexe hors menu : TickPanelClick(PC, DeltaTime) n'a qu'un appel, .cpp l. 1043, placé après le retour de la l. 1027-1037 (if (!bSexOn || bMenuOnScreen) { …; return; }) et conditionné par PC non nul. Chemins vérifiés : (1) hors sexe : bSexOn faux, retour avant l'appel (l. 1027) ; (2) menu à l'écran : idem ; (3) GI nul : bSexOn faux ; (4) PC nul : pas d'appel, retour l. 1045.
  • Hors sexe, rien ne change (tous les chemins) :
    • appel : voir ci-dessus, jamais hors sexe/menu ;
    • H1 étendu : la détection du clic molette (l. 1001-1003) est dans le else de if (!bSexOn) (l. 987-993 / 993-1026) : évaluée pendant le sexe seulement ; hors sexe, le bloc H1/H2 n'est pas exécuté ; en sexe, bClick ne sert qu'au retour du focus (l. 1007-1011, 1019, 1020-1025) ;
    • remises à faux de bMiddleWasDown : l. 991 (hors sexe) et l. 1031 (hors sexe ou menu) : écritures d'un membre qui n'a aucun autre lecteur que TickPanelClick ; aucune valeur de PC/GI/Room, aucun appel, aucune ligne de journal ;
    • TickPanelClick ne lit ni n'écrit rien du PC hors la bascule du panneau (ProcessEvent conditionné par le clic) ;
    • la fonction ne s'appelle pas depuis un autre ticker (Grep TickPanelClick : .cpp 1043, 1122 ; .h 139).
  • Seuil et mouvement : appui = bMiddle && !bMiddleWasDown : MiddleHeld = 0, position de départ, bMiddleHadMouse, bMiddleMoved = false (l. 1128-1134) ; tenu ou relâché = else if (bMiddleWasDown) : MiddleHeld += DeltaTime (durée réelle, DeltaTime non borné ; le Step borné de TickSexCamera n'est pas utilisé) et bMiddleMoved |= bMiddleHadMouse && bPanelMouse && Distance(...) > PanelClickMaxMove (l. 1138-1140) ; bascule à !bMiddle && MiddleHeld <= 0,2 && !bMiddleMoved (l. 1141) : 0,2 s inclus, 5 px exclus (>), conformes à la SPEC § 5.4 (« moins de 0,2 s », « 5 px près »). bMiddleWasDown = bMiddle; en fin (l. 1155).
  • Appel de l'événement : PC->FindFunction(PCLeftMouseSexOn) (l. 1143), Toggle && Toggle->NumParms == 0 (l. 1144), PC->ProcessEvent(Toggle, nullptr) (l. 1147) précédé de la ligne Sexe : panneau bascule (clic molette, %.2f s) ; sinon la ligne panneau non bascule, evenement LeftMouseSexOn absent du PC (l. 1151). Aucun paramètre, aucun pointeur nul déréférencé (Toggle testé d'abord).
  • Un appui commencé dans un menu ne bascule rien : pendant un menu, bMiddleWasDown est remis à faux à chaque image (l. 1031) et TickPanelClick n'est pas appelé ; un appui né pendant le menu n'a donc aucun MiddleHeld valide. Réserve N1 : si le bouton est encore enfoncé à la fermeture du menu, l'appui est compté comme neuf à cet instant.
  • H1 étendu, justification : MenuOnScreen rend vrai quand un champ de saisie garde le focus (.cpp l. 340-341) ; sans retour du focus, bMenuOnScreen reste vrai et TickSexCamera sortirait avant TickPanelClick. Le retour du focus recalcule bMenuOnScreen dans la même image (l. 1024) avant le test de la l. 1027. Établi par lecture. Que IsPressed(MiddleMouseButton) soit vu par PlayerInput quand Slate a le focus : incertain (même hypothèse que pour les clics gauche et droit de H1), essai CAM-83.
  • Verdict PASS-STATIC (réserve N1).

4. Diff lu en entier

Chaque diff a été lu en entier (git diff --no-index -U2, 5 diffs .cpp et 3 diffs .h non vides) ; hunks du diff global (lignes de l'état final) :

Fichier Hunks Contenu Point
.cpp 133-134 CoupleRootAboveSurface H3
.cpp 176-178 PCLeftMouseSexOn, PanelClickMaxHold, PanelClickMaxMove H5
.cpp 465-500 CoupleLowestRoot (H3), ComponentMesh, IsAdmittedBed, DescribeComponent (H2) H2, H3
.cpp 526, 530 CoupleSpotFree : IsAdmittedBed, DescribeComponent H2
.cpp 969-973 ObstacleReach H1
.cpp 991, 998-1003, 1031, 1041-1044 remises à faux, clic molette dans H1, appel H5
.cpp 1122-1155 TickPanelClick H5
.cpp 1210-1241 TraceDown H3
.cpp 1288 LastSurfaceRefused.Reset() H4
.cpp 1296-1306 prise : GrabSurface, GrabHeight, ligne de prise H3
.cpp 1361-1395 glisser : hauteur (H3), bSurface et branche G-f (H4) H3, H4
.h 91-92 commentaire de GrabHeight H3
.h 121-122 LastSurfaceRefused H4
.h 137-144 TickPanelClick et 5 membres H5
  • Aucun ajout non demandé : tout hunk se rattache à un point de la fiche (H1 à H5) ; ni STG ni STE touchés (contrôle 15).
  • Non-régression B2p-C à B2p-G : G1 (CoupleSpotFree, LastSpotBlocker, Blocker) garde la condition et le déplacement ; E2 conservé (H3) ; E1/E3 (TickCoupleBounds) hors diff ; G-a à G-d (TickSceneCamera, RecenterSceneCamera hors le seul renommage), ComputeLabel et G3 (NotifyCoupleTeleported, l. 1321-1335) hors diff ; H1/H2 de B2p (focus clavier) élargis au clic molette (annoncé) ; B2p-D : le glisser perd sa dépendance à bGrabHasHeight (voulu, SPEC § 5.2) et GrabHeight ne sert plus qu'au journal (vérifié : seule lecture l. 1305). Aucune régression trouvée.

5. Verdict

Point Verdict
H1 (N7) PASS-STATIC
H2 PASS-STATIC
H3 (G-e) PASS-STATIC, écart SPEC jugé acceptable
H4 (G-f) PASS-STATIC
H5 (G-g) PASS-STATIC
Contrôles transverses (types, includes, signatures, C4456/C4458/C4459 par lecture) PASS-STATIC
Global PASS-STATIC : aucun défaut bloquant. Non couvert : la compilation (BLOCKED-compilation, à confier à BUILD COMMUN-12) et tout comportement en jeu (reporté V7).

6. Points à confirmer par l'utilisateur (cases)

  • BUILD COMMUN-12 : LifePlay compile avec 0 erreur et 0 avertissement nouveau sous V7 ; LPCameraModeSubsystem.cpp doit figurer dans les unités compilées (C4456 / C4458 / C4459 et résidu N6).
  • Accepter l'écart de H3 à la SPEC (racine la plus basse du couple plutôt que racine du meneur).
  • CAM-81 : glisser sur le lit de face et d'en haut ; vérifier la ligne couple pris … racine a +N cm de la surface … et que la tête de lit ne porte pas le couple (R1) ; vérifier qu'aucune ligne surface refusee ne désigne le plafond, un tapis ou un décor mince quand on vise le sol (N3).
  • CAM-82 : commode refusée, une seule ligne surface refusee … ; décider si une seconde visite de la même surface refusée dans la même prise doit réécrire la ligne (N2).
  • CAM-83 : clics gauche et droit sans bascule (partie PC, MCP sous UE-GO) ; Espace et clic molette basculent ; ligne panneau bascule (clic molette, …) ; ProcessEvent hors tick du monde sans anomalie (R4) ; clic molette vu par PlayerInput quand le filtre a le focus.
  • CAM-84 : hors sexe, rien ne change (Espace en pause, clics et molette comme avant).
  • CAM-85 : ligne place refusee … (maillage) présente.

Tours : 25 / 40

Constats

Défauts bloquants

Aucun (preuves : contrôles 1 à 15 et lecture point par point).

Défauts non bloquants

# Lieu Constat Niveau de preuve État
N1 .cpp l. 1031 et 1128, 1041-1045 bMiddleWasDown est remis à faux à chaque image de menu : si le bouton est encore enfoncé à la fermeture du menu, l'appui est compté comme neuf et un relâcher en 0,2 s au plus bascule le panneau. De même, si PC est nul quelques images (l. 1041-1045), bMiddleWasDown garde sa dernière valeur. Un clic qui commence et finit dans la même image n'est pas vu (IsPressed est un état). Tous cas très rares. estimé (lecture) ouvert, négligeable
N2 .cpp l. 1381-1385 LastSurfaceRefused n'est pas remis à vide quand le curseur repasse sur une place admise : revenir sur la même surface refusée dans la même prise n'écrit pas de seconde ligne (même traitement que LastSpotBlocker, l. 1396-1399). Lu comme « une ligne par changement de surface refusée ». établi (lecture) ouvert, à confirmer (case CAM-82)
N3 .cpp l. 1218-1241, 1376 Avec le canal Pawn, toute surface touchée au-dessus du sol qui n'est pas un lit est refusée en pièce connue : un tapis ou décor mince plus haut que le dessus de Room.Box, un plafond ou volume bloquant Pawn sous Pivot.Z + 150 (départ du tracé), ou la tête de lit (R1) peuvent refuser des places que le joueur juge valides. Le journal nomme le composant (surface refusee …), donc l'essai tranche d'emblée. incertain (essai : CAM-81 et CAM-82) ouvert
N4 .h l. 91-93 Le commentaire de GrabHeight est posé au-dessus de bGrabHasHeight (antérieur à H, B2p-D). Sans effet sur la compilation. établi ouvert, cosmétique
N5 IMPL Écart de gabarit : voir § 1 (tête commune, « Écarts à la SPEC », « Ce qui n'a pas été fait », « Points à confirmer en VAL », Tours : absents). Le contenu est exact : MD5, lignes, périmètre et noms recomptés. Le diff global (136 / 24, 12 / 1) n'est pas dans l'IMPL ; il est ici. établi non bloquant, à reporter à l'auteur
N6 compilation Le résidu C4459 vient des en-têtes moteur : un nom Toggle, Surface, Lowest ou Refused déclaré en portée globale par un en-tête inclus par CameraMode n'est excluable que par compilation. Grep sur Engine/Classes/*.h : rien. Toggle a un précédent de local compilé sous V7 (LPModInfoSubsystem.cpp:113, 288). estimé (faible probabilité) à trancher par BUILD

Non mesuré

Compilation et avertissements ; tout essai en jeu (CAM-81 à CAM-85, reportés V7) ; géométrie réelle des assets (tête de lit, tapis, plafond sur le canal Pawn) ; vue de IsPressed(MiddleMouseButton) par PlayerInput quand Slate a le focus ; contexte d'appel de ProcessEvent (R4) ; absence d'écriture dans STG / STE prouvée par les dates seulement (les instantanés ne contiennent pas ces fichiers) ; 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-H-VAL.md (dépôt du jeu, branche ue5.8). Fichiers modifiés par la VAL : ce livrable seulement ; fichiers de travail dans le dossier temporaire de session (scratchpad), hors dépôt.

À verser

  • lp-validate § 1 : le crochet ecriture-gardien refuse le signe « supérieur à » même dans un echo entre guillemets (lu comme redirection, flèche -> comprise) ; écrire « vers » dans les étiquettes. Un script .ps1 du dossier temporaire, lancé par pwsh -NoProfile -File, évite les pièges de $ dans Git Bash (foreach($x in …) y est mangé).
  • lp-validate § 1 : pour des instantanés « d'avant » successifs, le point Hn est le diff Hn vers H(n+1) ; la somme brute des diffs par point peut dépasser le global d'une ligne ajoutée puis remplacée par un point suivant : le noter et le recompter (+137 / −25 contre +136 / −24 ici, écart expliqué), l'égalité tient sur le net (lignes LF) et sur le .h.
  • lp-ue-compat / lp-build : UObject::FindFunction(FName) const (Object.h:1258), ProcessEvent(UFunction*, void*) (Object.h:1490), UFunction::NumParms (Class.h:2574), FHitResult::GetComponent() const (HitResult.h:227) vérifiés en 5.8.