Fiche CAM-Tocc-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-Tocc-VAL : témoin d'occlusion C2, validation statique (2026-10-14) Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8` (HEAD 0745917). Sans UE et sans compilation : lecture, diff et en-têtes du moteur seulement. Aucun fichier modifié hors ce livrable. Verdict maximal possible : `PASS-STATIC`. ## 1. Fiches amont relues - `Docs\Migration\etat\CAM\CAM-COMMUN5-DIAG.md` § 2 (a) (spécification du témoin, l. 70-73) et § 4 (l. 108 : T-occ, « journal seulement » ; l. 112-114 : MD5 avant écriture). - Aucun livrable IMPL `CAM-Tocc-*` : `Glob Docs/Migration/etat/CAM/*Tocc*` ne rend aucun fichier, et `Grep Tocc|T-occ` sous `Docs` ne trouve que `CAM-COMMUN5-DIAG.md:108` et `BOSS-ETAT.md:54`. Les sources livrées servent d'amont (écart 1). - Code lu en entier : `Source\LifePlay\LPCameraModeSubsystem.cpp` (503 lignes) et `.h` (66 lignes). ## 2. Points à confirmer des IMPL amont sans objet : aucune IMPL CAM-Tocc n'existe sous `etat\CAM` (voir section 1), donc aucun point à confirmer à reprendre. Les points à confirmer ouverts par cette VAL sont en section 6. ## 3. Critères rejoués | # | Contrôle | Commande ou lecture | Résultat | Verdict | |---|---|---|---|---| | C1 | MD5 après (.cpp, .h) | `Tools\FileCheck.ps1 -Path` (copie 5.8) | `.cpp` FE9236BD138A46BBC2B63027977B3CD9 ; `.h` 7DA111CB65B267A9E2F7482CBA368BE9 : égaux aux valeurs attendues | PASS | | C2 | MD5 avant (sauvegarde CAM-Tocc) | même commande sur `D:\Game\LifePlay_Backup_5.5\CAM-Tocc\Source\LifePlay\` | `.cpp` D8981529E3773FBF50BB7FC1C91BC91F ; `.h` 2E1E245883960C2AFFC667A9A48FD96D : égaux à ceux du DIAG (`CAM-COMMUN5-DIAG.md:114`) | PASS | | C3 | Fins de ligne, BOM | sortie FileCheck : `LF`, CRLF 0, CR 0, BOM `-`, alertes=0 pour les 4 fichiers (503 / 66 / 456 / 62 LF) | LF sans BOM, inchangé par rapport à l'avant | PASS | | C4 | Pas de chemin de profil Windows | Grep des motifs de dossier de profil (dossier utilisateur, données d'application) et du nom de compte sur `LPCameraModeSubsystem.*` : 0 résultat | aucun | PASS | | C5 | Diff = ajouts seulement | `git diff --no-index --numstat` : `.cpp` 47 ajouts, 0 retrait (`--stat` : « 47 insertions(+) ») ; `.h` 4 ajouts, 0 retrait | aucune ligne existante modifiée ni retirée | PASS | | C6 | Conformité à la spécification § 2 (a) | lecture `.cpp:267-306` | voir détail ci-dessous | PASS-STATIC | | C7 | Déclaration et appel | `.h:55-58` ; `.cpp:217-221` ; Grep `WitnessOcclusion` sur `Source` | méthode privée déclarée dans le `.h` (l. 56) ; un seul appel (`.cpp:220`), dans le bloc `if (Label == LabelC2)`, juste après `WitnessC2(DeltaTime);` (l. 219) | PASS-STATIC | | C8 | `WitnessC2` et ligne `Touche C` inchangés | diff : aucune ligne retirée, aucun ajout dans `WitnessC2` (l. 308-384) ni `LogSwitchKey` (l. 252-265) ; l'insertion est avant `WitnessC2` | inchangés, ligne « aucun » (`Indicateur de camera : aucun`, l. 198) intacte | PASS | | C9 | Compilation probable | en-têtes du moteur, voir détail ci-dessous | aucune erreur prévisible | PASS-STATIC | | C10 | Coût | analyse, voir ci-dessous | acceptable | PASS-STATIC | | C11 | Comportement et Blueprint | diff | aucun changement de comportement du jeu : lecture par réflexion et un tracé, une ligne de journal ; aucun Blueprint, aucun asset | PASS-STATIC | ### Détail C6 (spécification, point par point) - C2 seulement : appel dans la branche `Label == LabelC2` (`.cpp:217`), libellé `Camera: C2 Follow` (`.cpp:59`). Établi. - Toutes les 0,5 s : `OcclusionInterval = 0.5f` (`.cpp:66`), armé à la ligne 274 avant toute sortie anticipée, décrémenté par le `DeltaTime` du ticker (`.cpp:269`). Estimé : le ticker tourne à 0,1 s (`TickInterval`), la cadence réelle est un multiple de 0,1 s, donc 0,5 s à l'image près. - Tracé Visibility complexe, Room compris : `FCollisionQueryParams Query(SCENE_QUERY_STAT(LPCameraOcclusion), true)` (`.cpp:288`), `ECC_Visibility` (`.cpp:296`). Room n'est pas ajouté aux acteurs ignorés (contraste : `WitnessC2` l. 342 ignore Room). Établi. - Départ caméra, arrivée tête : `CamLoc = Cam->GetActorLocation()` avec `Cam` = `Room.CamRoom` ; `Head = Mesh->GetSocketLocation("head")` (`.cpp:285-286`, `HeadSocket` l. 60). Tracé de `CamLoc` vers `Head` (l. 296). Établi. - Acteurs ignorés : `Player` (l. 289), `Cam` (l. 290), `GI.Wall` (l. 291-294, nom `Wall` l. 56). Établi. - Au plus une ligne par seconde : sur contact, `OcclusionCooldown = OcclusionLogInterval = 1.f` (l. 302) ; sans contact, le délai reste à 0,5 s. Donc le tracé suivant un contact a lieu 1 s plus tard (et non 0,5 s) : voir constat 3. - Format : « Temoin C2 : joueur masque par %s (%s) de %s, visible=%d, a %.0f cm de la camera (camera a %.0f cm de la tete) » (l. 303) = texte de la demande. Composant, maille (`-` si ce n'est pas un `UStaticMeshComponent`), acteur, visible 0 ou 1, `Hit.Distance`, `FVector::Dist(CamLoc, Head)`. Établi. - Journal seulement : aucun effet de bord hors `OcclusionCooldown` et le `UE_LOG`. Établi. ### Détail C9 (compilation probable, lue dans les en-têtes de `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\…`) - Includes : `Components/StaticMeshComponent.h` et `Engine/StaticMesh.h` ajoutés (`.cpp:10-11`). `GetStaticMesh() const` existe : `Engine\Classes\Components\StaticMeshComponent.h:453`, retourne `TObjectPtr`. `Cast` sur un `const UPrimitiveComponent*` : type complet par ce même en-tête. - `GetNameSafe` : surcharge `const TObjectPtr&` (`CoreUObject\Public\UObject\UObjectBaseUtility.h:912`) et `const UObjectBaseUtility*` (l. 893). Un `TObjectPtr` temporaire convient à la surcharge gabarit (correspondance exacte). Le même appel existe déjà, compilé : `LPShowCoupleSubsystem.cpp:788` (`GetNameSafe(Shell->GetStaticMesh())`). - `FCollisionQueryParams(SCENE_QUERY_STAT(...), true)` : `SCENE_QUERY_STAT(Q)` se développe en **deux** arguments, `SCENE_QUERY_STAT_NAME_ONLY(Q), SCENE_QUERY_STAT_ONLY(Q)` (`Engine\Public\CollisionQueryParams.h:39`). L'appel devient `(FName, TStatId, true)`, qui choisit le constructeur `FCollisionQueryParams(FName, const TStatId&, bool bInTraceComplex = false, const AActor* = NULL)` (l. 238) : `true` est bien `bTraceComplex`. Même forme avec `false` déjà compilée dans `WitnessC2` (`.cpp:341`). - `LineTraceSingleByChannel(FHitResult&, const FVector&, const FVector&, ECollisionChannel, const FCollisionQueryParams&, …) const` (`Engine\Classes\Engine\World.h:2161`, dans `class UWorld`, l. 931). Appelée sur `UWorld* World` non const : valide (`World = GI->GetWorld()`, l. 276, même forme que `WitnessC2` l. 316). - `UPrimitiveComponent::IsVisible() const` : hérité de `USceneComponent::IsVisible() const` virtuel, section publique (`Engine\Classes\Components\SceneComponent.h:853`, section publique des lignes 400 à 872) ; `PrimitiveComponent.h` ne le redéfinit pas (Grep : 0). Appelé via `const UPrimitiveComponent*` : valide. - `FHitResult::GetComponent() const` rend `UPrimitiveComponent*` (`Engine\Classes\Engine\HitResult.h:227`) ; `GetActor() const` rend `AActor*` (l. 211). L'affectation à `const UPrimitiveComponent*` est valide. - `UE_LOG` : 6 spécificateurs (`%s`, `%s`, `%s`, `%d`, `%.0f`, `%.0f`), 6 arguments : 1 `*GetNameSafe(Comp)` (`const TCHAR*`) ; 2 ternaire `MeshComp ? *GetNameSafe(...) : TEXT("-")` (`const TCHAR*` des deux côtés, même motif que `.cpp:264`) ; 3 `*GetNameSafe(Hit.GetActor())` ; 4 `Comp && Comp->IsVisible() ? 1 : 0` (int, précédence : `(Comp && …) ? 1 : 0`) ; 5 `Hit.Distance` (float, promu en double) ; 6 `FVector::Dist(...)` (double, UE5). Les temporaires `FString` vivent jusqu'à la fin de l'expression complète. - `.h` : la méthode est déclarée dans la section `private`, hors UHT (pas de `UFUNCTION`), après `WitnessCooldown`. `OcclusionCooldown` initialisé à `0.f`. Aucun nouvel include nécessaire dans le `.h`. - Espace de noms : constantes ajoutées dans `LPCameraModeLocal` (nommé, `.cpp:29`) : pas de risque C2374 (`OcclusionInterval`, `OcclusionLogInterval` définies une seule fois, Grep sur `Source`). - Limite : lecture seule, pas de compilation. Verdict `PASS-STATIC`, pas `PASS` (rien n'a été compilé ici). ### Détail C10 (coût) - Un tracé de ligne **complexe** unique, `LineTraceSingleByChannel`, toutes les 0,5 s (1 s après un contact), en C2 seulement. Un seul rayon sur le thread de jeu, limité à la caméra et à la tête (quelques mètres). Ce coût est du même ordre que celui du `WitnessC2` existant (trois balayages sphériques, mais seulement sous 1 m) et reste à la cadence d'un ticker à 0,1 s sans travail à chaque image. - Lectures par réflexion : 4 `ReadObject` (GI.Room, GI.PlayerCharacter, Room.CamRoom, GI.Wall) = recherche de propriété par nom, toutes les 0,5 s : négligeable. - Hors C2 (autres modes, menu ouvert) : aucun appel. Acceptable pour un témoin de diagnostic. Estimé (aucune mesure de temps d'image faite). Rappel du DIAG (`CAM-COMMUN5-DIAG.md:80`) : le coût du tracé complexe est un risque pour l'évitement B2l (sondes fréquentes), pas pour ce témoin à 2 Hz. ## 4. Diff lu en entier - `.cpp` : `git diff --no-index` avant/après lu en entier : 4 blocs, 47 ajouts, 0 retrait (2 includes, 2 constantes + 1 commentaire, 1 appel, 1 fonction de 40 lignes). - `.h` : 1 bloc, 4 ajouts, 0 retrait (méthode + membre et leurs deux commentaires). - Total : 2 fichiers, 51 lignes ajoutées. Recompte par une seconde méthode : `--numstat` (`.h` : `4 0`) et `--stat` (`.cpp` : 47 insertions) ; longueurs des fichiers 456 → 503 (+47) et 62 → 66 (+4) par FileCheck. - Les deux fichiers sont **non suivis** par git dans la copie 5.8 (`git status --short` : `??` ; `git ls-files` : 0) : le diff contre HEAD est impossible, la sauvegarde `LifePlay_Backup_5.5\CAM-Tocc` (MD5 identiques à ceux du DIAG) sert d'état avant. - Fichiers modifiés d'autres sous-systèmes visibles au `git status` (`LPDebugSubsystem.*`, `LPDirectControlSubsystem.*`, `LPOutdoorRoomSubsystem.*`, `LifePlay.cpp`) : hors périmètre de cette fiche, non lus, non attribués à CAM-Tocc. ## 5. Verdict **PASS-STATIC** pour le témoin d'occlusion C2 (CAM-Tocc). Tous les contrôles (C1 à C11) sont conformes ; aucune erreur de compilation prévisible d'après les en-têtes du moteur 5.8 ; aucune compilation ni essai en jeu n'a été exécuté. Le verdict `PASS` plein exige le BUILD et un essai en C2. ## 6. Points à confirmer par l'utilisateur - [ ] BUILD de `LifePlay` (éditeur 5.8, `Tools\Build.ps1`) : 0 erreur, module `LifePlay` dans les unités compilées. Non rejoué ici. - [ ] Essai en jeu en C2 (pub assis, le cas C5-2) : vérifier la ligne `Temoin C2 : joueur masque par …` quand le joueur disparaît, et son absence quand il est visible. - [ ] Accepter que, après un contact, le tracé suivant ait lieu 1 s plus tard (constat 3). Tours : 11 / 40 ## Constats Écarts 1. Aucun livrable IMPL `CAM-Tocc-*` ni fiche dans `etat\CAM` (Glob vide) : la traçabilité du « qui a écrit, instantané, fichiers à commiter » manque ; la VAL s'est appuyée sur le code et la sauvegarde `CAM-Tocc`. À fournir avant le ticket du gardien. 2. `Source\LifePlay\LPCameraModeSubsystem.cpp` et `.h` sont non suivis dans la copie 5.8 (`??`) : le ticket devra les ajouter (`A`), ce que la fiche n'annonce pas. Observations (sans verdict négatif) 3. Cadence : sur contact, `OcclusionCooldown` passe à 1 s, donc un tracé tous les 1 s tant que le joueur est masqué (au plus une ligne par seconde, conforme) ; le tracé suivant sans contact revient à 0,5 s. Cohérent avec « au plus une ligne par seconde ». 4. `visible=` reflète `UPrimitiveComponent::IsVisible()` (drapeau du composant), pas `AActor::IsHidden()` : un acteur masqué par `SetActorHiddenInGame` peut donner `visible=1`. La spécification demande seulement `visible=<0|1>` ; le témoin `WitnessC2` garde son champ `cache=` pour l'acteur. 5. Seuls `Player`, `Cam` et `GI.Wall` sont ignorés (conforme) : un acteur attaché au joueur (vêtement, accessoire) ou un autre personnage dans l'axe serait signalé comme masquant. Utile comme preuve, à lire avec le nom de l'acteur de la ligne. 6. Le membre et la méthode ajoutés sont placés entre deux membres de données du `.h` (style seulement, sans effet).