Fiche ERR145-DIAG — Niveau N3− — Modèle prévu non précisé par la fiche | utilisé claude-opus-5-5 — Agent lp-diag — Skills sk-protocol, ue-cpp-build, lp-commandlets, lp-paths # ERR145-DIAG : C1 assis, le joueur se voit sans tête (2026-10-20) Ordonnateur : Big Boss (local_104ff22d). Lecture seule : ni copie UE, ni MCP, ni compilation. Seul fichier écrit : ce livrable. ## 1. Symptôme et reproduction - Symptôme (établi) : « bug à référencer en C1 quand assis. On se contemple décapité. à voir en build 2 » (`etat/BOSS/BOSS-JOURNAL.md:872`, 2026-10-20 13:08 ; capture non enregistrée, `:873`) ; ERR-145 (`Docs/Registre_Erreurs.md:152`) ; `etat/BOSS/COMMUN-17-BILAN.md:65`. - Journal de l'essai (établi, horodatage du journal en UTC, heure locale = UTC + 2 ; 13:08 locale = 11:08 UTC) : `D:\Game\LifePlay_Build_5.8_Development_COM17\Windows\LifePlay\Saved\Logs\LifePlay.log` - l. 1869 (11:04:41) : touche C, `CamC1On=1 C1Hidden=1` ; l. 1874-1894 : C1 debout, souris, tangage jusqu'à -60 ; - l. 1898 (11:05:08) : sortie de C1 (tangage remis à 0) ; l. 1972 (11:05:33) : joueur assis au meuble `_3` ; l. 1975 : menu `Scene_C` ; l. 1978 (11:05:34) : « place `_3` libérée sans marche, joueur posé à gauche à 80 cm du siège » ; - l. 2111 (11:05:40.384) : fin du blocage par `Scene_C` ; l. 2329 (11:05:40.724) : retour en C1 (`C1 souris`, `minuterie RoomCam active=1`), **aucune ligne « Touche C » entre l. 1869 et l. 2472** : la vue C1 est remise par le jeu à la fermeture du menu, joueur **debout** (posé à côté du siège) ; - l. 2387-2388 (11:05:42.397) : le joueur s'assoit au meuble `_20` : « racine Genesis8Female à 128 cm du bassin, **à 104 cm du composant** » ; - l. 2390-2417 (11:05:43 → 11:07:56) : C1 assis, regard vers le bas (tangage -38 à -60) ; l. 2472 (11:08:12) : touche C vers C2. La plainte (13:08 locale) tombe dans cette fenêtre. - Recette de reproduction proposée (non jouée) : en jeu, **debout**, touche C jusqu'à l'indicateur « C1 First person » ; aller s'asseoir sur une chaise (clic) **sans quitter C1** ; regarder autour et vers le bas. Puis, toujours assis, C → C2 → C1. Puis se lever (Z) en C1. ## 2. Hypothèse testée (une seule) H1 (reformulation précise de l'hypothèse de la fiche) : en C1, la tête est masquée par `HideBoneByName(Mesh, 'neckLower')` et la caméra est recalée à chaque passage de la minuterie sur le milieu des os `lEye` / `rEye`. Or un os masqué sort des os requis et **n'est plus animé** : `lEye` et `rEye` restent dans la pose (en espace composant) qu'ils avaient au moment du masquage. Entré debout en C1 puis assis, le joueur garde une « tête fantôme » à la position debout, la caméra la suit, et le corps assis (déplacé par l'animation de siège, racine à environ 1 m du composant) est vu de l'extérieur, sans tête. ## 3. Essai et résultat brut Lectures (aucune exécution du jeu ; aucune commande ne modifie de fichier). ### 3.1 Ce que fait C1 (établi) - Entrée en C1 (`Room.ApplyRoomCamera`, voie C1, `C1Hidden` faux) : attache `FirstPersonSM` à `head`, transformée relative, `SetWorldLocation(FirstPersonSM, 0,5 × (GetSocketLocation(Mesh,'lEye') + GetSocketLocation(Mesh,'rEye')))` calculé **avant** le masquage, détache, `HairSM.SetVisibility(faux)`, `HideBoneByName(PlayerCharacter.Mesh,'neckLower',PBO_None)`, `C1Hidden := vrai`, une image d'attente, `SetViewTargetWithBlend` (`etat/CAM/CAM-B1-IMPL.md:22`, `:87`, `:104-109`). B1d note déjà : « un os masqué sort des os requis et ne bouge plus » (`CAM-B1-IMPL.md:107`). - Si `C1Hidden` est déjà vrai, la voie saute toute remise et va directement à la vue (`etat/CAM/CAM-COMMUN6-DIAG.md:155`). - Minuterie `UpdateRoomCam` (0,016 s) : en C1 avec vue = `PlayerCharacter`, `83.then → 512 K2_SetWorldLocation(FirstPersonSM, milieu de lEye/rEye) → 540 K2_SetWorldRotation` (`etat/CAM/CAM-B2s-IMPL.md:137`, `CAM-B2k-IMPL.md:75-76`, `:102`). La position de la caméra suit donc en permanence les os des yeux… **qui sont sous `neckLower`, donc masqués** (`lEye`, `rEye` sous `head`, sous `neckLower` : `CAM-B1-IMPL.md:110-111`). - `RestoreC1` (sortie de C1 ou vue ≠ joueur) fait `UnHideBoneByName(neckLower)` et `C1Hidden := faux` (`CAM-B1-IMPL.md:29-30`, `:87-88`). - C++ : `LPCameraModeSubsystem.cpp` ne masque rien (aucun `HideBone`, `SetOwnerNoSee`) ; il règle le tangage (`TickC1Pitch`, `:770-806`), remet `C1Hidden` à faux en entrée de scène (`:752-760`) et écrit le témoin C1 (`:1196-1231`). Grep `VisibilityBasedAnimTickOption|AlwaysTickPose` sur `Source` : 0 résultat. ### 3.2 Moteur 5.8 installé (établi, `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Engine\Private\`) - `Components/SkeletalMeshComponent.cpp:2255-2258` : `ComputeRequiredBones` appelle `ExcludeHiddenBones` (« Purge invisible bones and their children »). - `Components/SkeletalMeshComponent.cpp:2321-2332` : un os n'est gardé que s'il est `BVS_Visible`, **ou** si `VisibilityBasedAnimTickOption == AlwaysTickPoseAndRefreshBones` ; les autres sont retirés des os requis. - `Components/SkeletalMeshComponent.cpp:2271-2275` puis `SkinnedAsset.cpp:522-536` : l'espace composant n'est recalculé que pour les os de cette liste : les os masqués gardent leur dernière transformée. - `Components/SkinnedMeshComponent.cpp:2433` : une position d'os = transformée en espace composant × `LocalToWorld` (branche avec leader ; branche sans leader estimée identique) : `GetSocketLocation('lEye')` = pose figée × position actuelle du composant. - `Character.cpp:125` : `ACharacter` règle `Mesh->VisibilityBasedAnimTickOption = AlwaysTickPose`, pas `…AndRefreshBones`. - Surcharge dans Person : `rg -a -c 'AlwaysTickPose|VisibilityBasedAnimTickOption'` sur `Content/BPs/Person.uasset`, `Room.uasset`, `FirstPerson.uasset` → `0`, `0`, `0` (sortie : `Person.uasset: 0` / `Room.uasset: 0` / `FirstPerson.uasset: 0`). ### 3.3 Le témoin C1 ne peut pas voir le défaut (établi) `WitnessC1` mesure la distance caméra ↔ milieu de `LeftEyeSocket` / `RightEyeSocket` du même `Mesh` (`LPCameraModeSubsystem.cpp:1214-1215`) : les mêmes os figés que ceux sur lesquels la minuterie pose la caméra. La distance reste proche de 0 même quand la caméra est à 1 m du corps. Grep `Temoin C1` sur les journaux COM16 à COM23 : 0 ligne (seules lignes trouvées : `unOccupyFnt`, `C1 : tangage … remis a 0`, pile `PlayFntAnim`). L'absence de ligne ne prouve donc rien. ### 3.4 Même mécanisme déjà vu, attribué à autre chose (établi pour les constats, rapprochement estimé) - ERR-106 (C5-3) : « après être entré assis puis changé de pose, la caméra est dans le buste » ; résidu après B2k « décalage après un lever » (`Registre_Erreurs.md:113`). - ERR-111 : « C1 à côté de la tête après un lever … un cycle C corrige » (`Registre_Erreurs.md:118`). Un cycle C passe par `RestoreC1` (démasque) puis remasque dans la pose nouvelle : c'est exactement ce que prédit H1. B2k (recalage de position) et B2s (orientation) ne pouvaient pas corriger le fond, puisqu'ils relisent les os figés. - ERR-145 est le cas inverse : entré debout, puis assis. ## 4. Verdict : confirmée sur le mécanisme (estimé fort), non prouvée en jeu - **Établi** : la caméra C1 suit `lEye` / `rEye` à chaque passage (§ 3.1) ; ces os sont masqués en C1 ; le moteur 5.8 retire les os masqués des os requis et ne recalcule plus leur pose sauf `AlwaysTickPoseAndRefreshBones` (§ 3.2) ; le maillage du joueur est en `AlwaysTickPose` (défaut `ACharacter`, aucune surcharge lue dans les assets) ; à 11:05:40 le joueur revient en C1 debout puis s'assoit à 11:05:42 avec une racine à 104 cm du composant (journal COM17 l. 1978, 2329, 2388). - **Estimé** : la tête (et donc la caméra) reste dans la pose debout en espace composant ; le corps, déplacé par l'animation de siège, est vu de dehors et sans tête ; le témoin est aveugle (§ 3.3). Cause d'ERR-145 = os masqués figés. - **Inconnu** : le moment exact de l'entrée en C1 à 11:05:40 (pas de ligne « Touche C », remise par le jeu à la fermeture de `Scene_C`, voie exacte non lue) ; la capture (absente) ; la distance réelle caméra ↔ corps ; l'effet du double tampon des transformées (`bDoubleBufferedComponentSpaceTransforms`) sur la valeur figée (sans effet sur la conclusion, estimé). - Hypothèse concurrente non exclue, H2 : caméra bien aux yeux, mais le tangage vers le bas (nouveau depuis A1, journal l. 2390-2417 jusqu'à -60) montre le cou coupé à `neckLower`. Essai qui départage : § 5, point 2 (cycle C assis). ### Endroit du correctif **C++ (`Source/LifePlay/LPCameraModeSubsystem.cpp`)**, recommandé. Ni GI, ni PC, ni Person : la révision unique de GI n'est pas concernée. ### Correctifs proposés | Option | Constat traité | Coût | Risque | |---|---|---|---| | **A (recommandée)** C++ : quand la vue est C1 (`LabelC1`), mettre `PlayerCharacter.Mesh.VisibilityBasedAnimTickOption = AlwaysTickPoseAndRefreshBones` (valeur d'origine gardée, remise en sortie de C1, même endroit que `ResetC1PitchOnExit`, `:811-827`) ; les os masqués restent requis et animés (`SkeletalMeshComponent.cpp:2322`), le masquage visuel reste par l'état de visibilité des os | ERR-145, et le fond d'ERR-106 / ERR-111 (lever, rasseoir) | 15 à 25 lignes, une compilation, aucune écriture d'asset | à confirmer en SPEC : le changement d'option doit forcer un recalcul des os requis (`RecalcRequiredBones` ou équivalent, API 5.8 à lire) ; une image de retard possible au passage ; un os masqué non requis est-il bien rendu masqué quand il redevient requis (`SkeletalRender.cpp:477-487`, cité par `CAM-B1e-CART.md:68-72`) | | B Room (MCP) : dans `UpdateRoomCam`, poser la caméra depuis un os visible (parent de `neckLower`) plus un décalage | ERR-145 en partie | ~5 nœuds, instantané Room, VAL BP | décalage homme / femme à calibrer ; regard qui ne suit plus les mouvements de tête de l'animation | | C C++ ou PC : au changement d'`OccupyingFnt` en C1, rejouer `RestoreC1` puis `ApplyRoomCamera` (le cycle C qui corrige déjà) | assis / levé seulement | ~20 lignes C++ (appel d'événements par nom) | une image de transition ; ne couvre pas les autres changements de pose (fin de scène, CharGen) | Ajout à toute option : corriger le témoin C1 pour qu'il mesure la caméra contre un os **non masqué** (ex. distance caméra ↔ os parent de `neckLower`, attendu 15 à 40 cm), sinon il reste aveugle. ## 5. Hypothèse suivante proposée, et à quel niveau 1. **Preuve en jeu attendue** (build suivant, C++ seulement, avant ou avec l'option A) : ligne de témoin nouvelle, une par seconde en C1 : distance caméra ↔ os visible (`neckLower` parent), `assis=`, racine ↔ composant. Avant correctif, après la recette du § 1 : distance > 60 cm assis (attendu 15-40 cm debout) ; après correctif A : 15-40 cm dans tous les états. 2. **Essai qui départage H1 / H2** (sans build, par l'utilisateur) : debout → C1 → s'asseoir : vue décapitée de l'extérieur ; puis, assis, C → C2 → C1 : si la vue revient aux yeux et que le corps n'est plus vu « de dehors », H1 ; si la coupe du cou reste visible en regardant vers le bas, H2 (alors masquer en plus le haut du buste ou borner le tangage bas, N2). 3. SPEC de l'option A : N3 (`lp-impl`), lecture de l'API 5.8 de recalcul des os requis avant écriture. Tours : 21 / 30 ## Constats - `FIX1-F7-PRE.md` (currentAnimation non vidé au lever) : sans lien établi avec ERR-145 (concerne l'animation « look » et `meshZ` au lever, `FIX1-F7-PRE.md:10`, `:18`). - `KEY2-CART-02.md` : aucune mention de C1 ni de la tête (Grep `C1|tête|neckLower` : 0 ligne). - Dumps `L6f_dump_*` / `CAM-B1-PRE_LPDumpFunction_*` non relus directement : les nœuds de C1 sont cités depuis les livrables CAM qui les ont relus (`CAM-B1-IMPL.md`, `CAM-B2k-IMPL.md`, `CAM-B2s-IMPL.md`, `CAM-B1e-CART.md:61-63`). - Fichiers à commiter : `A Docs/Migration/etat/REL/ERR145/ERR145-DIAG.md` (dépôt du jeu, `ue5.8`).