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

434 lines
35 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-SPEC — Niveau N3 — Modèle prévu non précisé par la fiche | utilisé claude-opus-5-5 — Agent lp-impl — Skills sk-protocol, ue-cpp-build, lp-build, lp-ue-compat
# ERR145-SPEC : C1 assis, os masqués figés — option A, témoin C1 et réglages de vue (2026-10-21)
Ordonnateur : Big Boss (local_104ff22d). Étude seule : aucun code modifié, aucune compilation, ni copie UE, ni MCP, ni commit.
Seul fichier écrit : ce livrable. Amont : `ERR145-DIAG.md` (H1), confirmation de l'utilisateur du 2026-10-21 07:11, puis
fait nouveau relayé par Big Boss (capture 34 : le cycle C assis ne corrige pas la vue).
## 1. Objectif mesurable
### 1.1 Ce qui est établi avant d'écrire
- **H1 (mécanisme)** : Room masque `neckLower` sur `PlayerCharacter.Mesh` (`etat/CAM/CAM-B1-IMPL.md:87`) ; un os masqué sort des os
requis sauf si `VisibilityBasedAnimTickOption == AlwaysTickPoseAndRefreshBones` (`SkeletalMeshComponent.cpp:2321-2332`) ;
`lEye` / `rEye` (sous `head`, sous `neckLower`) gardent leur pose en espace composant ; `UpdateRoomCam` pose la caméra au milieu
de ces os à chaque passage (`CAM-B2k-IMPL.md:75`, nœud `SWL_512`). Confirmé en jeu par l'utilisateur à 07:11 (debout → C1 → assis).
- **Journal de l'essai du 2026-10-21** (`D:\Game\LifePlay_Build_5.8_Development_COM23\Windows\LifePlay\Saved\Logs\LifePlay.log`,
heure UTC, locale = UTC + 2) :
- l. 1725 (05:08:56) : entrée en C1 debout ; l. 1736 : action « Sit » (`have_a_meal_in`) ; l. 1755-1757 (05:09:13) : assis au
meuble `_196`, « racine Genesis8Female à 77 cm du bassin, à 9 cm du composant » ; l. 1758-1768 : C1 assis, tangage -11 à -57,
lacet de contrôle de -175 à +187 (vue décapitée, cas ERR-145) ;
- l. 1781 (05:11:21) C1 → C2 (`C1Hidden=0`, donc `RestoreC1` passé) ; l. 1784 (05:12:10) → C3 ; l. 1787-1790 (05:12:14.978) → C1,
toujours assis (aucun `unOccupyFnt` entre l. 1757 et l. 1809) ;
- l. 1791-1792 (05:12:15 à 05:12:16) : **tangage -0,1 puis -2,1**, lacet de contrôle +133 puis +86 ; plus aucune ligne « C1 souris »
jusqu'à la sortie de C1 en l. 1800 (05:14:01). `TickC1Pitch` n'écrit qu'au mouvement vertical de la souris
(`LPCameraModeSubsystem.cpp:782-786`) et c'est le seul code qui change le tangage hors sortie (`:790`, `:811-828`).
- **Capture 34** (description de Big Boss) : C1 assis après le cycle, surface de peau cylindrique collée à gauche, haut violet et collier
vus de très près à droite. Elle tombe dans la fenêtre 05:12:15 → 05:14:01 (07:12 → 07:14 locales), seule fenêtre « C1 assis après
un cycle » du journal (estimé : l'heure exacte de la capture n'est pas dans le journal).
### 1.2 Conséquences du fait nouveau (établi / estimé)
- **Établi** : après le cycle, les yeux ont été relus dans la pose assise du moment (os démasqués en C2 et C3, puis remasqués à
l'entrée en C1 ; la voie C1 lit les yeux avant le masquage, `CAM-B1-IMPL.md:104-107`). C'est l'état que l'option A maintient en
permanence pour une pose immobile. La vue reste mauvaise : **l'option A seule n'est pas prouvée suffisante**.
- **Établi** : le tangage valait environ -2° pendant la capture. « Borner le tangage » ne traite pas ce cas, et l'hypothèse H2
du DIAG (regard vers le bas sur la coupe du cou) n'explique pas la capture 34.
- **Estimé** (géométrie) : la largeur horizontale vue à 90° (FOV de `FirstPersonSM`, `CAM-B1e-CART.md:79`) ne couvre verticalement
qu'environ ±29° en 16:9. Pour qu'une épaule ou un cou remplisse la gauche de l'image à un tangage de -2°, il faut que le point de vue
soit **à hauteur d'épaule ou de cou**, pas à hauteur des yeux d'une tête droite. Deux lectures restent ouvertes :
- **L1, gel à une phase basse** : la pose « repas » plonge peut-être la tête vers l'assiette en boucle. Le remasquage a figé les
yeux à un instant où la tête était baissée. A corrige le gel, mais la caméra suivra alors la tête qui plonge et remonte.
- **L2, point de vue trop près du corps visible**, yeux vivants : tête très penchée, regard de côté (lacet de contrôle +86, orientation
du buste inconnue), ou sommets du cou à poids mixtes étirés (voir R8). Le tout avec un plan proche à **1 cm**
(`Config/DefaultEngine.ini:50`, contre 10 cm par défaut dans `BaseEngine.ini:361`) : toute peau à plus de 1 cm est dessinée.
- **Inconnu** : le contenu de l'animation `fnt_new017-f-0-use` (l. 1748) ; l'orientation du buste pendant la capture ; la distance
réelle entre la caméra et la peau.
### 1.3 Objectifs (critères chiffrés au § 7)
1. **A** : en C1, les os masqués du joueur restent requis et animés. Le témoin montre « yeux-buste » dans la plage debout,
assis comme debout, et la recette du DIAG § 1 ne montre plus le corps décapité vu de dehors.
2. **Témoin** : une ligne par seconde en C1. Elle mesure la vue contre un os **visible** (parent de `neckLower`) et contre l'avant du
buste. Elle donne aussi les yeux, `assis=`, l'écart racine ↔ composant, l'option d'animation et l'état du masquage.
3. **Réglages de vue C1** (partie C, sans effet par défaut) : plan proche propre à la vue C1 et décalage du point de vue vers l'avant
du buste, réglables à la console. Ils servent à trancher L1 / L2 dans la même build, sans second aller-retour.
4. PNJ, POV de sexe, C2, C3, Overview, studio : comportement inchangé.
## 2. Fichiers touchés, ordre
Ordre : (1) `LPCameraModeSubsystem.h`, (2) `LPCameraModeSubsystem.cpp`, (3) `LPRoomBoundsCameraModifier.h`, (4)
`LPRoomBoundsCameraModifier.cpp` (tous sous `D:\Game\LifePlay-master 5.8\Source\LifePlay\`). Les parties 3 et 4 portent seulement la
partie C. Elles débordent du périmètre de la fiche, qui ne nommait que le sous-système (écart, voir Constats). Si l'orchestrateur ne
retient pas C, on retire § 2.4, les appels à `SetC1View` et `CVarLPC1NearClip` / `CVarLPC1Forward` ; le témoin écrit alors `plan proche 0.0`.
Volume estimé : partie A ≈ 45 lignes ; témoin ≈ 60 lignes, dont 36 remplacées ; partie C ≈ 35 lignes. Total ≈ 140 lignes touchées
(≈ +115 / −30), aucune écriture d'asset, une compilation.
### 2.1 `LPCameraModeSubsystem.h`
- Ajouter `class USkeletalMeshComponent;` aux déclarations avancées (l. 17-25).
- Remplacer le commentaire du témoin C1 (l. 71) par : « Témoin C1 (ERR145) : une ligne par seconde en C1, vue mesurée contre le
parent de neckLower (os visible) et l'avant du buste ».
- Après `float C1Cooldown = 0.f;` (l. 74) :
```cpp
/** ERR145 (option A) : en C1, les os masques par Room restent requis et animes sur le maillage du joueur
* (AlwaysTickPoseAndRefreshBones) ; option d'origine rendue des que le libelle quitte C1 ou que le joueur change.
* Transmet aussi les reglages de vue C1 au modificateur (partie C). Appele a chaque image depuis TickSwitchKey. */
void TickC1BoneRefresh(bool bC1Now);
/** Maillage dont l'option a ete changee, et sa valeur d'origine (EVisibilityBasedAnimTickOption). */
TWeakObjectPtr<USkeletalMeshComponent> C1RefreshMesh;
uint8 C1SavedTickOption = 0;
```
### 2.2 `LPCameraModeSubsystem.cpp` : partie A
- Include : `#include "HAL/IConsoleManager.h"` (`TAutoConsoleVariable`, `IConsoleManager.h:2028`).
- Dans `namespace LPCameraModeLocal`, après la l. 108 :
```cpp
// ERR145 : os masque par Room en C1 (CAM-B1c) ; son parent (chestUpper attendu sur Genesis 8, lu a l'execution) est le
// repere visible du temoin ; les clavicules donnent l'avant du buste.
const FName NeckLowerBone(TEXT("neckLower"));
const FName LeftCollarBone(TEXT("lCollar"));
const FName RightCollarBone(TEXT("rCollar"));
// Avant horizontal du buste : perpendiculaire a la ligne des clavicules (UE : X avant, Y droite, Z haut, donc
// (droite - gauche) x haut = avant) ; a defaut, avant de l'acteur. OutFrom nomme la source pour le temoin.
FVector BodyForward(const ACharacter* Player, const USkeletalMeshComponent* Mesh, const TCHAR*& OutFrom)
{
OutFrom = TEXT("acteur");
FVector Forward = Player->GetActorForwardVector();
if (Mesh->GetBoneIndex(LeftCollarBone) != INDEX_NONE && Mesh->GetBoneIndex(RightCollarBone) != INDEX_NONE)
{
const FVector Across = Mesh->GetSocketLocation(RightCollarBone) - Mesh->GetSocketLocation(LeftCollarBone);
const FVector Candidate = FVector::CrossProduct(Across, FVector::UpVector).GetSafeNormal2D();
if (!Candidate.IsNearlyZero())
{
Forward = Candidate;
OutFrom = TEXT("clavicules");
}
}
return Forward.GetSafeNormal2D();
}
```
`BodyForward` doit être défini après les includes de `GameFramework/Character.h` et `Components/SkeletalMeshComponent.h`
(déjà en l. 20 et 26). Il reste dans l'espace nommé, avec appel qualifié (règle C2374, `lp-build` § 8).
- Après la fermeture de l'espace de noms (l. 728) :
```cpp
// ERR145 : bascules de l'essai en jeu (console, touche ² ou ~, DefaultInput.ini:166-168), sans recompiler.
static TAutoConsoleVariable<int32> CVarLPC1RefreshBones(TEXT("lp.C1RefreshBones"), 1,
TEXT("ERR145 : 1 = en C1, les os masques du joueur restent animes (option A) ; 0 = comportement d'avant (os figes)."));
static TAutoConsoleVariable<float> CVarLPC1NearClip(TEXT("lp.C1NearClip"), 0.f,
TEXT("ERR145 : plan proche de la vue C1, en cm (0 = plan global du projet, 1 cm)."));
static TAutoConsoleVariable<float> CVarLPC1Forward(TEXT("lp.C1Forward"), 0.f,
TEXT("ERR145 : avance du point de vue C1 vers l'avant du buste, en cm (0 = aucune)."));
```
- Nouvelle méthode, placée après `ResetC1PitchOnExit` (l. 828) :
```cpp
// ERR145 (option A, ERR145-SPEC § 2.2) : Room masque neckLower en C1 ; un os masque sort des os requis et sa pose reste
// figee (SkeletalMeshComponent.cpp:2321-2332), or UpdateRoomCam pose la camera sur lEye / rEye, sous neckLower. En C1,
// le maillage du joueur passe en AlwaysTickPoseAndRefreshBones : les os masques restent requis et animes, le rendu les
// garde masques (SkeletalRender.cpp:516-520). Le drapeau bRequiredBonesUpToDate = faux fait recalculer les os requis au
// prochain passage d'animation (SkeletalMeshComponent.cpp:1707-1712, 2879-2884), comme HideBone (:3950).
void ULPCameraModeSubsystem::TickC1BoneRefresh(bool bC1Now)
{
UGameInstance* GI = GetGameInstance();
ACharacter* Player = bC1Now ? Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter)) : nullptr;
if (!IsValid(Player))
{
Player = nullptr;
}
USkeletalMeshComponent* Mesh = (Player && CVarLPC1RefreshBones.GetValueOnGameThread() != 0) ? Player->GetMesh() : nullptr;
USkeletalMeshComponent* Applied = C1RefreshMesh.Get();
if (Applied && Applied != Mesh)
{
Applied->VisibilityBasedAnimTickOption = static_cast<EVisibilityBasedAnimTickOption>(C1SavedTickOption);
Applied->bRequiredBonesUpToDate = false;
UE_LOG(LogLifePlay, Log, TEXT("C1 : os du joueur rendus a l'option %d (sortie de C1, scene, joueur change ou lp.C1RefreshBones 0)"), C1SavedTickOption);
Applied = nullptr;
}
if (!Applied)
{
C1RefreshMesh.Reset();
}
if (Mesh && !Applied)
{
C1SavedTickOption = static_cast<uint8>(Mesh->VisibilityBasedAnimTickOption);
Mesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
Mesh->bRequiredBonesUpToDate = false;
C1RefreshMesh = Mesh;
UE_LOG(LogLifePlay, Log, TEXT("C1 : os masques du joueur gardes animes (option %d -> %d ; neckLower masque=%d)"),
C1SavedTickOption, static_cast<int32>(EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones),
Mesh->IsBoneHiddenByName(LPCameraModeLocal::NeckLowerBone) ? 1 : 0);
}
// Partie C (§ 2.4) : reglages de vue C1 transmis au modificateur a chaque image ; nul hors C1.
UWorld* World = GI ? GI->GetWorld() : nullptr;
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
APlayerCameraManager* Manager = PC ? PC->PlayerCameraManager.Get() : nullptr;
ULPRoomBoundsCameraModifier* Modifier = Manager ? Cast<ULPRoomBoundsCameraModifier>(Manager->FindCameraModifierByClass(ULPRoomBoundsCameraModifier::StaticClass())) : nullptr;
if (Modifier)
{
FVector Offset = FVector::ZeroVector;
const float Forward = CVarLPC1Forward.GetValueOnGameThread();
if (Player && Player->GetMesh() && Forward != 0.f)
{
const TCHAR* From = nullptr;
Offset = LPCameraModeLocal::BodyForward(Player, Player->GetMesh(), From) * Forward;
}
Modifier->SetC1View(Player, CVarLPC1NearClip.GetValueOnGameThread(), Offset);
}
}
```
- `TickSwitchKey`, après la l. 1264 (`ResetC1PitchOnExit(...)`) :
`TickC1BoneRefresh(FrameLabel == LPCameraModeLocal::LabelC1);`. Le libellé est calculé à chaque image (l. 1261). Il vaut C1 seulement
si la vue est `PlayerCharacter` et `CamC1On` (l. 2632). Pendant une scène, il vaut « Scene » ou « POV » (l. 2620-2627). L'entrée en
scène rend donc l'option dans la même image ; aucun autre point d'appel n'est nécessaire. Un menu ouvert en C1 garde le libellé C1
et l'option : voulu, car la vue reste C1 derrière le menu.
- `Deinitialize`, avant `RemoveViewBounds` (l. 744) : `TickC1BoneRefresh(false);` (rend l'option si le jeu se ferme en C1).
### 2.3 `LPCameraModeSubsystem.cpp` : témoin C1 (remplace `WitnessC1`, l. 1196-1231)
```cpp
// ERR145 : une ligne par seconde en C1, sans condition. L'ancien ecart aux yeux etait aveugle : Room pose la camera sur ces
// memes yeux (ERR145-DIAG § 3.3). Reperes : parent de neckLower (os visible) et avant du buste (clavicules).
void ULPCameraModeSubsystem::WitnessC1(float DeltaTime)
{
C1Cooldown = FMath::Max(0.f, C1Cooldown - DeltaTime);
if (C1Cooldown > 0.f)
{
return;
}
C1Cooldown = LPCameraModeLocal::C1LogInterval;
const UGameInstance* GI = GetGameInstance();
const UWorld* World = GI ? GI->GetWorld() : nullptr;
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
const ACharacter* Player = Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter));
USkeletalMeshComponent* Mesh = IsValid(Player) ? Player->GetMesh() : nullptr;
const USceneComponent* Camera = IsValid(Player) ? Cast<USceneComponent>(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerFirstPersonSM)) : nullptr;
const FName Chest = Mesh ? Mesh->GetParentBone(LPCameraModeLocal::NeckLowerBone) : NAME_None;
if (!PC || !Mesh || !IsValid(Camera) || Chest.IsNone())
{
return;
}
const FVector ChestAt = Mesh->GetSocketLocation(Chest);
const FVector Eyes = (Mesh->GetSocketLocation(LPCameraModeLocal::LeftEyeSocket) + Mesh->GetSocketLocation(LPCameraModeLocal::RightEyeSocket)) * 0.5f;
const FVector CameraAt = Camera->GetComponentLocation();
const FVector ViewAt = PC->PlayerCameraManager ? PC->PlayerCameraManager->GetCameraLocation() : CameraAt;
const TCHAR* From = nullptr;
const FVector Forward = LPCameraModeLocal::BodyForward(Player, Mesh, From);
const FVector Right = FVector::CrossProduct(FVector::UpVector, Forward);
const FVector Rel = ViewAt - ChestAt;
const FRotator CameraRotation = Camera->GetComponentRotation();
const FVector Root = Mesh->GetBoneLocation(Mesh->GetBoneName(0));
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
UE_LOG(LogLifePlay, Log, TEXT("Temoin C1 : vue-%s %.0f cm (avant %+.0f, droite %+.0f, haut %+.0f) ; yeux-buste %.0f cm ; camera-yeux %.0f cm ; vue-camera %.0f cm ; lacet vue-buste %+.0f (%s) ; tangage %+.0f ; assis=%d ; racine-composant %.0f cm ; option os %d ; cou masque=%d ; plan proche %.1f ; minuterie=%d"),
*Chest.ToString(), Rel.Size(), FVector::DotProduct(Rel, Forward), FVector::DotProduct(Rel, Right), Rel.Z,
FVector::Dist(Eyes, ChestAt), FVector::Dist(CameraAt, Eyes), FVector::Dist(ViewAt, CameraAt),
FRotator::NormalizeAxis(CameraRotation.Yaw - Forward.Rotation().Yaw), From, FRotator::NormalizeAxis(CameraRotation.Pitch),
IsValid(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerOccupyingFnt)) ? 1 : 0,
FVector::Dist(Root, Mesh->GetComponentLocation()), static_cast<int32>(Mesh->VisibilityBasedAnimTickOption),
Mesh->IsBoneHiddenByName(LPCameraModeLocal::NeckLowerBone) ? 1 : 0, CVarLPC1NearClip.GetValueOnGameThread(),
LPCameraModeLocal::TimerActive(IsValid(RoomObject) ? RoomObject : nullptr, LPCameraModeLocal::RoomCamTimer, World) ? 1 : 0);
}
```
- Constantes devenues orphelines (`C1Interval`, `C1MaxEyeDistance`, `C1MaxPitch`, `C1MaxYawGap`, l. 104-108) : l'IMPL les cherche par
Grep et retire celles qui ne servent plus. Le préfixe « Temoin C1 : » est gardé pour les recherches de journal.
- **Plages attendues** (estimées sur les proportions Genesis 8, non mesurées ; la VAL les cale sur la première ligne debout) :
| Champ | Debout, A actif | Assis, A actif | A coupé (`lp.C1RefreshBones 0`) après debout → assis |
|---|---|---|---|
| `yeux-buste` | 20 à 35 cm (référence R0) | R0 ± 5 cm (la chaîne du cou est rigide) | s'écarte de R0 de plus de 10 cm (COM17 : racine à 104 cm du composant) |
| `camera-yeux` | 0 à 3 cm (minuterie de 0,016 s, une image de retard) | 0 à 3 cm | 0 à 3 cm (la caméra suit les yeux figés : aveugle, comme l'ancien témoin) |
| `vue-buste` haut / avant | +15 à +30 / 0 à +15 cm | idem si la tête est droite ; haut < +10 = tête baissée (L1 / L2) | quelconque |
| `vue-camera` | 0 (= `lp.C1Forward` si réglé) | idem | idem |
| `lacet vue-buste` | libre (souris X) ; au-delà de ±90 = regard par-dessus l'épaule | idem | idem |
| `option os` / `cou masque` | 0 / 1 | 0 / 1 | 1 / 1 (valeur d'`ACharacter`, `Character.cpp:125` cité par le DIAG) |
### 2.4 Partie C : réglages de vue C1 (`LPRoomBoundsCameraModifier.h/.cpp`)
- `.h`, section publique, à côté de `SetBounds` (l. 28) :
```cpp
/** ERR145-SPEC § 2.4 : vue C1 (cible de vue = joueur) : plan proche propre en cm (0 = plan global) et decalage du point
* de vue en coordonnees monde. Joueur nul = rien. Rafraichi a chaque image par LPCameraModeSubsystem. */
void SetC1View(AActor* InPlayer, float InNearClip, const FVector& InOffset);
```
Section privée : `TWeakObjectPtr<AActor> C1View; float C1NearClip = 0.f; FVector C1Offset = FVector::ZeroVector;`
- `.cpp` : `SetC1View` recopie les trois valeurs. Dans `ModifyCamera`, après le bloc Studio (l. 42-47) et **avant** le filtre des
l. 50-54 (qui rend `false` pour toute vue non bornée, C1 comprise) :
```cpp
// ERR145 : vue C1 seulement (le joueur est aussi cible de vue en POV : SetC1View recoit nul hors C1).
const AActor* C1 = C1View.Get();
if (C1 && CameraOwner && CameraOwner->GetViewTarget() == C1)
{
if (C1NearClip > 0.f)
{
InOutPOV.PerspectiveNearClipPlane = C1NearClip;
}
InOutPOV.Location += C1Offset;
}
```
- Pourquoi un modificateur et pas `FirstPersonSM` : `UCameraComponent` n'a pas de plan proche en perspective en 5.8, seulement
`OrthoNearClipPlane` (`CameraComponent.h:84-86`). Son décalage additif (`:221`, `:311`, `:330-334`) resterait posé sur un composant
partagé avec la POV de sexe (`CAM-B1e-CART.md:89`). Le champ `FMinimalViewInfo::PerspectiveNearClipPlane` (`CameraTypes.h:92-94`) est
pris par le calcul de projection (`CameraStackTypes.cpp:342`, appelé par `LocalPlayer.cpp:1285`). Il ne touche que la vue, pas le
plan global. Le modificateur est toujours installé, quel que soit le libellé (`LPCameraModeSubsystem.cpp:873-881`).
## 3. Repli
- **Sans build** : `lp.C1RefreshBones 0` à la console rend l'option d'origine dès l'image suivante (§ 2.2) ; `lp.C1NearClip 0` et
`lp.C1Forward 0` (valeurs par défaut) annulent la partie C. En Shipping, sans console, ces variables gardent leur défaut.
- **Code** : instantané pris par la session principale avant l'IMPL (`Tools\Snapshot.ps1 -Lot ERR145`). Retour arrière seulement
proposé (`Tools\Restore.ps1 -Lot ERR145`). Les quatre fichiers ne touchent ni Room, ni Person, ni aucun asset.
## 4. Risques R#, questions Q#
### 4.1 L'option A suffit-elle ? (réponse à Big Boss)
**Non prouvé, et la capture 34 dit plutôt non pour la pose « repas ».** A est **nécessaire** : le mécanisme est établi et confirmé en
jeu, et c'est lui qui donne la vue décapitée de dehors (COM17 ; COM23, l. 1757-1768). Mais le cycle C a produit, pour cette pose, l'état
que A maintient, et la vue est restée mauvaise à un tangage de -2°. Mesures proposées par Big Boss, jugées sur le code :
| Mesure | Effet sur la capture 34 | Verdict |
|---|---|---|
| Borner le tangage | aucun : tangage -0,1 / -2,1 (l. 1791-1792), borne actuelle -60 (`LPCameraModeSubsystem.cpp:132`) jamais atteinte dans la fenêtre | écartée (et contraire à la décision A1-R « tous les axes », `:766`) |
| Masquer plus haut (`neckUpper`) | laisse `neckLower` visible, donc de la peau **plus près** des yeux | écartée (aggrave, estimé) |
| Masquer plus bas (`chestUpper`) | masque aussi les clavicules et les bras, s'ils descendent de `chestUpper` (estimé, hiérarchie Genesis 8 non relue) ; c'est un changement de Room (Blueprint), pas du C++ | écartée |
| Plan proche propre à C1 (C, `lp.C1NearClip`) | retire toute peau à moins de N cm du point de vue (projet : 1 cm, `DefaultEngine.ini:50`) ; ne déplace rien | **1er complément**, réglage en jeu |
| Décalage vers l'avant (C, `lp.C1Forward`) | sort le point de vue du volume du buste quel que soit le lacet, s'il suit l'avant **du buste** ; s'il suivait le regard, il pousserait la vue dans l'épaule en regardant de côté | **2e complément**, réglage en jeu |
Recommandation : livrer A, le témoin et C (C sans effet par défaut) dans la **même build**. On choisit les valeurs par défaut de C
d'après les essais T0 (sans build) et T6 (§ 7.2). Décision par les chiffres du témoin, A actif, assis, vue mauvaise :
- `yeux-buste` hors de R0 ± 5 → A n'agit pas (option non posée, voir `option os`) : retour à l'IMPL ;
- `haut` < +10 (tête baissée, L1 ou L2) → `lp.C1Forward` 10 à 20, ou plan proche 10 à 15 ;
- `haut` ≥ +15 et |lacet| > 90 → regard par-dessus l'épaule : plan proche ; une borne de lacet relative au buste serait une mesure
nouvelle, hors de cette fiche ;
- `haut` ≥ +15, |lacet| ≤ 60, vue mauvaise → peau étirée du cou (R8) : plan proche ;
- vue bonne → A suffit ; C reste à 0.
### 4.2 Risques
- **R1** : A seul ne corrige pas la capture 34 (§ 4.1) ; couvert par C et le témoin.
- **R2** : avec A, la caméra suit la tête animée : plongée vers l'assiette si la pose « repas » en a une (L1), petits mouvements de
tête des poses de repos. Gêne possible ; réponse par `lp.C1Forward` ou plus tard un lissage (Q2).
- **R3** : 1 à 3 images de gel à l'entrée en C1. Room masque, attend une image, puis change la vue (`CAM-B1-IMPL.md:104-105`) ; le
libellé C1 n'est vu qu'après ce changement (l. 2632). Les yeux viennent d'être lus : écart négligeable (estimé).
- **R4** : A demande deux recalculs des os requis par passage en C1, un à l'entrée et un à la sortie, le même chemin que
`HideBone` / `UnHideBone` (`SkeletalMeshComponent.cpp:3950`, `:3983`). `RecalcRequiredBones` remet la pose locale à la référence
(`:2369`) avant l'évaluation de la même image (`:1707-1712` puis `:2879-2884`). Pas d'image en pose de référence attendue (estimé).
C'est la raison pour laquelle on n'appelle **pas** `RecalcRequiredBones` hors du passage d'animation.
- **R5** : coût : les os de la tête (visage compris) redeviennent évalués en C1, comme hors C1. `AlwaysTickPoseAndRefreshBones`
rafraîchit même sans rendu (`SkinnedMeshComponent.cpp:1805-1808`), mais le corps est rendu en C1 : surcoût nul attendu (estimé).
- **R6** : avec un plan proche au-dessus de 1 cm, un mur à moins de N cm du point de vue est traversé du regard. Les passes qui liraient
`GNearClippingPlane` directement plutôt que la vue ne sont pas relues (incertain). Le risque n'existe que si `lp.C1NearClip` > 0.
- **R7** : la POV de sexe n'est pas couverte. PC masque aussi `neckLower` sur `CurrentActor` (`CAM-B1e-CART.md:62-63`) et y attache
`FirstPersonSM` à `head`, un os sous l'os masqué : même gel possible (estimé, non observé). Voir Q3.
- **R8** : les sommets à poids mixtes du cou (`chestUpper` et `neckLower`) sont dessinés entre leur place et l'**origine** de la
matrice du parent. En effet `ApplyScale(0)` = `ScaleMatrix * M` annule les trois axes et garde la ligne de translation
(`Matrix.inl:897-905` ; `SkeletalRender.cpp:486`, `:519`). Ce point est solidaire de `chestUpper`, à l'endroit où se trouvait
l'origine du modèle en pose de liaison, donc loin sous le buste. D'où un « tube » de peau étiré (les « lambeaux » de
`CAM-B1e-CART.md:72`). A ne le change pas, C ne le cache que près de la vue. Candidat (estimé) pour la « surface de peau
cylindrique » de la capture 34.
- **R9** : sans os `lCollar` / `rCollar`, `BodyForward` prend l'avant de l'acteur ; le témoin l'écrit (`acteur`). Une pose de meuble
qui tourne le corps par rapport à l'acteur fausserait alors `avant` et `lacet` : sans effet sur A.
- **R10** : compilation unitaire. Les noms neufs sont uniques (`CVarLPC1*`) ou dans l'espace nommé `LPCameraModeLocal` (`lp-build` § 8).
### 4.3 Questions pour l'utilisateur
- **Q1** : quelles valeurs par défaut pour `lp.C1NearClip` et `lp.C1Forward` dans la build d'essai ? Proposition : 0 (aucun effet).
Réglage à la console pendant T6, puis valeur retenue écrite dans le code, ou dans `[SystemSettings]` de `DefaultEngine.ini`, pour la
Shipping.
- **Q2** : la vue C1 doit-elle suivre les mouvements de tête des animations, au risque de plonger avec la tête dans la pose « repas » ?
Si non, la position doit venir d'un os du buste plus un décalage fixe (option B du DIAG, voir `vue-buste` du témoin).
- **Q3** : étendre A à la POV de sexe (personne filmée = joueur ou PNJ) dans une fiche à part ?
## 5. Interdits
- Aucun changement de Room, PC, GI, Person ni d'asset ; aucun MCP ; aucun changement du plan proche global (`DefaultEngine.ini:50`).
- Ne toucher à aucun maillage autre que `GI.PlayerCharacter->GetMesh()`. Les PNJ gardent leur option : la propriété est par instance
de composant (`SkinnedMeshComponent.h:793-794`).
- Pas d'appel direct à `RecalcRequiredBones` ni à `RecalcRequiredCurves` (R4) ; pas de décalage additif sur `FirstPersonSM` (§ 2.4).
- Ne pas retirer ni modifier `LPStringCompat`, `LPMeshCompat`, `LPNavigationHelpers` et voisins (`lp-ue-compat` § 4).
- Pas de compilation par l'IMPL (fiche BUILD à `lp-builder`), pas de commande git qui écrit, modification par Edit uniquement.
## 6. Valeurs moteur lues dans le moteur installé
Moteur `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\` (préfixe omis : `Engine/` sauf mention).
- `Classes/Components/SkinnedMeshComponent.h:94-113` : `EVisibilityBasedAnimTickOption` ; `AlwaysTickPoseAndRefreshBones` = « Always
Tick and Refresh BoneTransforms whether rendered or not ». Lignes `:787-794` : `VisibilityBasedAnimTickOption` est publique,
`UPROPERTY(… BlueprintReadWrite, Config …)`, sans setter (Grep sur `Classes/Components` : seulement `:794`, `:797-798`, et des
commentaires). Lignes `:1156`, `:1165` : `GetBoneIndex`, `GetBoneName` ; `:1263` : `GetParentBone` ; `:2014` : `GetBoneLocation` ;
`:2153`, `:2161`, `:2171` : `HideBoneByName`, `UnHideBoneByName`, `IsBoneHiddenByName` (non const) ; `:1759-1772` : états de
visibilité.
- `Classes/Components/SkeletalMeshComponent.h:894-896` : `bRequiredBonesUpToDate` est public et transitoire ; à faux, les os requis
sont recalculés au prochain passage. Lignes `:1862-1868` : `RecalcRequiredBones` (public) ; `:1885` : `ExcludeHiddenBones` ;
`:1898` : `RecalcRequiredCurves`. Aucune API `MarkRequiredCurvesDirty` à appeler : `RecalcRequiredBones` marque les courbes à jour
(`SkeletalMeshComponent.cpp:2400`) après le recalcul des instances d'animation (`:2383-2396`).
- `Private/Components/SkeletalMeshComponent.cpp:1707-1718` (passage d'animation) et `:2879-2890` (`RefreshBoneTransforms`) :
recalcul si le drapeau est faux. Lignes `:2246-2258` : les os demandés par les suiveurs sont ajoutés, puis les os masqués purgés.
Lignes `:2296-2336` : `ExcludeHiddenBones` garde un os s'il est `BVS_Visible` **ou** si l'option vaut
`AlwaysTickPoseAndRefreshBones` (`:2321-2322`). Lignes `:2358-2408` : `RecalcRequiredBones`. Lignes `:3933-3965` et `:3967-3983` :
`HideBone` / `UnHideBone` mettent le drapeau à faux, sauf sur un suiveur (`:3942-3945`, `:3976-3979`).
- `Private/Components/SkinnedMeshComponent.cpp:2119-2123` : un suiveur ne met pas à jour sa visibilité. Lignes `:2124-2163` :
propagation `BVS_HiddenByParent`. Lignes `:4424-4448`, `:4482-4500` : masquage par nom et par index. Lignes `:2413-2434` et
`:2468-2474` : position d'un os = transformée en espace composant (celle du leader pour un suiveur) × `LocalToWorld`.
Lignes `:1805-1808`, `:1881-1897` : rafraîchissement selon le rendu ou l'option.
- `Private/SkeletalRender.cpp:453`, `:458-466` : le rendu parcourt les os actifs de la LOD (`ActiveBoneIndices`), **pas** les os
requis. Lignes `:483-487` (suiveur, états du leader) et `:516-520` (sans leader) : un os non visible reçoit la matrice du parent à
l'échelle 0. **Un os masqué mais requis reste invisible au rendu.** Établi.
- `Core/Public/Math/Matrix.inl:897-905` : `ApplyScale` = `ScaleMatrix * M` ; la ligne de translation de M est gardée (R8).
- `Classes/GameFramework/Character.h:487` : `GetMesh() const` rend un pointeur non const.
`Classes/Camera/PlayerCameraManager.h:705` : `GetCameraLocation() const`. `Classes/Camera/CameraModifier.h:95` :
`ModifyCamera(float, FMinimalViewInfo&)`.
- `Classes/Camera/CameraTypes.h:92-94`, `:178`, `:216-219` : `PerspectiveNearClipPlane` (négatif = plan global, -1 par défaut).
`Private/Camera/CameraStackTypes.cpp:342` (et `:210`) : pris par la projection. `Private/LocalPlayer.cpp:1285` : appel de la vue du
joueur. `Classes/Camera/CameraComponent.h:84-86` : seul un plan proche orthographique existe sur la caméra ;
`Private/Camera/CameraComponent.cpp:448-461` : décalage additif.
- `Private/UnrealEngine.cpp:19090-19102` : commande console `r.SetNearClipPlane <cm>`, minimum 1, 20 sans argument, réglage
**global**. Aucune garde de compilation lue entre les l. 19050 et 19102 ; présence en Development estimée.
- `Core/Public/HAL/IConsoleManager.h:2028` : `TAutoConsoleVariable(const TCHAR* Name, const T& DefaultValue, const TCHAR* Help, uint32 Flags = ECVF_Default)`.
- Configuration : `Engine/Config/BaseEngine.ini:361` `NearClipPlane=10.0` ; projet `Config/DefaultEngine.ini:50`
`NearClipPlane=1.000000` ; console `Config/DefaultInput.ini:166-168` (`Tilde`, `²`).
- Leader et suiveurs dans le jeu : Room masque sur `PlayerCharacter.Mesh` = `CharacterMesh0` (`CAM-B1-IMPL.md:87` ;
`CAM-B1e-CART.md:48`, `:61`). Ce maillage est le leader de `HairSM`, `PenisSM` et des tenues (`SetLeaderPoseComponent(Mesh)`,
`CAM-B1e-CART.md:52-57`). Les suiveurs lisent les transformées et la visibilité du leader : A ne demande rien sur eux.
## 7. Critères de VAL (commande → attendu)
### 7.1 Contrôles de code et de build (`lp-validator`, après BUILD par `lp-builder`)
1. `git -C "D:\Game\LifePlay-master 5.8" diff --stat` → les 4 fichiers du § 2 seulement, ou 2 si C n'est pas retenue.
2. Grep `VisibilityBasedAnimTickOption` sur `Source/` → occurrences dans `LPCameraModeSubsystem.cpp` seulement. Grep
`RecalcRequiredBones|ApplyScale|AddAdditiveOffset` sur `Source/LifePlay` → 0.
3. `.\Tools\Build.ps1 -Lot ERR145` (LifePlayEditor) → code 0, module `LifePlay` compilé, 0 C4996 nouveau, « autres lignes : 20 ».
Puis build Development empaquetée neuve (`BuildDebug.ps1`, `-ArchiveDir` neuf) pour l'utilisateur.
4. Journal de l'essai : `.\Tools\LogStats.ps1 -Log <LifePlay.log> -Pattern 'Temoin C1', 'C1 : os'` → une ligne « gardes animes » par
entrée en C1, une ligne « rendus » par sortie, et des lignes « Temoin C1 » à 1 s d'écart en C1.
### 7.2 Cahier de tests pour l'utilisateur
Essai dans une build donnée ; console = touche ² ; chaque ligne rapporte « vue correcte / non », plus une capture si non.
- **T0, sans build, sur COM23, maintenant** (oriente le défaut de C, Q1) :
- T0a : s'asseoir à la même chaise « repas », en C1. Faire trois cycles C (C2 → C3 → C1) espacés de 5 s, en regardant à l'horizontale.
La vue change d'un cycle à l'autre (plus ou moins basse) → L1 (gel à une phase de la boucle) ; toujours la même → L2.
- T0b : dans la vue mauvaise, `r.SetNearClipPlane 10`, puis `r.SetNearClipPlane 20` ; noter si la peau proche disparaît.
Remettre ensuite `r.SetNearClipPlane 1` (valeur du projet), ou relancer le jeu.
- T0c : même essai sur une chaise sans repas (autre action « Sit »).
- **T1, recette du DIAG § 1** (build ERR145) : debout → C1 → s'asseoir sans quitter C1 → regarder autour, en bas, à l'horizontale.
Attendu : vue depuis les yeux, plus de corps décapité vu de dehors. Témoin : `yeux-buste` = R0 ± 5, `option os 0`, `cou masque=1`.
- **T2, preuve par bascule** : même recette avec `lp.C1RefreshBones 0` → le défaut d'origine revient, et `yeux-buste` s'écarte de R0 de
plus de 10 cm. Puis `lp.C1RefreshBones 1` → la vue revient aux yeux dans la seconde.
- **T3, lever en C1** (ERR-111, ERR-106) : assis en C1, se lever (Z) sans quitter C1. Attendu : caméra aux yeux, pas à côté de la tête ;
témoin : `yeux-buste` = R0 ± 5 et `assis=0`.
- **T4, cycle C** debout et assis : aucun écart ; une ligne « C1 : os … rendus » à chaque sortie de C1.
- **T5, PNJ et scènes inchangés** : en C2, PNJ assis et en marche (tête visible, aucun changement) ; lancer une scène depuis C1 (ligne
« rendus » à l'entrée), POV sur le partenaire (tête normale), retour en C1 après la scène.
- **T6, compléments** (si T1 assis reste mauvais, pose « repas ») : `lp.C1NearClip 10`, puis 15 ; remettre à 0 ; `lp.C1Forward 10`,
puis 20 ; remettre à 0. Noter le réglage qui donne une vue correcte et recopier trois lignes « Temoin C1 » de chaque réglage.
### 7.3 Effet attendu sur ERR-106 et ERR-111
- **ERR-111** (`Registre_Erreurs.md:118`). Volet « C1 à côté de la tête après un lever … un cycle C corrige » : même mécanisme dans le
sens inverse (yeux figés assis, corps levé). **Corrigé par A** (estimé fort), preuve par T3. Volets « vue au plafond au retour d'une
scène » (orientation, B2s) et « à-coup à la sortie de CharGen » : sans effet attendu de A.
- **ERR-106** (`Registre_Erreurs.md:113`). Résidu « décalage après un lever » : **corrigé par A** (estimé fort). Le recalage de B2k à
chaque passage de minuterie lit enfin des yeux vivants. La partie principale (caméra posée une seule fois) était déjà corrigée par B2k.
- **ERR-145** (`Registre_Erreurs.md:152`) : volet « entré debout puis assis » corrigé par A (T1, T2) ; volet « capture 34 » (assis,
après cycle) : A plus un complément C, selon T0 et T6.
Fichiers à commiter :
- `A Docs/Migration/etat/REL/ERR145/ERR145-SPEC.md` (dépôt du jeu, branche `ue5.8`)
Tours : 30 / 60
## Constats
- Écart au périmètre de la fiche : la partie C touche `LPRoomBoundsCameraModifier.h/.cpp`, non nommés par la fiche. Raison : aucun
plan proche par vue n'existe sur `UCameraComponent` en 5.8 (§ 2.4). Partie séparable, décision de l'orchestrateur.
- Écart au DIAG : la plage du témoin « 15 à 40 cm » (`ERR145-DIAG.md:103`) devient « R0 ± 5 », calée sur la première ligne debout de la
build. Les 20 à 35 cm sont une estimation Genesis 8 non mesurée.
- Le journal COM23 contient aussi, à l'action « Sit », trois `Divide by zero: Divide_DoubleDouble` de GI (l. 1740-1745) : sans lien
établi avec ERR-145.
- Il reste à vérifier, par MCP en lecture (hors fiche) : la hiérarchie Genesis 8 sous `chestUpper` (`get_bone_children`), et
l'existence de `lCollar` / `rCollar` sur Michael et Bridget. Le témoin écrit aussi `clavicules` ou `acteur`.