LifePlay-Revival/Docs/Migration/etat/REL/ERR145/ERR145-DIAG.md

123 lines
12 KiB
Markdown
Raw Normal View History

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`).