LifePlay-Revival/Docs/Migration/etat/REL/ERR145/ERR145-DIAG.md
2026-10-21 09:36:04 +02:00

122 lines
12 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 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`).