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

12 KiB
Raw Blame 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).