742 lines
51 KiB
Markdown
742 lines
51 KiB
Markdown
|
|
Fiche ERR145-SPEC-02 — 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-02 : caméra C1 stable — ancre sur le buste, lacet recentré, témoin refait (2026-10-21)
|
|||
|
|
|
|||
|
|
Ordonnateur : Big Boss (local_104ff22d). SPEC complémentaire de `ERR145-SPEC.md`, étude seule : aucun code modifié, aucune
|
|||
|
|
compilation, ni copie UE, ni MCP, ni commit. Seul fichier écrit : ce livrable.
|
|||
|
|
Amont : `ERR145-DIAG.md` (H1), `ERR145-SPEC.md` (option A, témoin, partie C), essai T0 de l'utilisateur sur la build COM23
|
|||
|
|
(2026-10-21, 08:13-08:25 locales), décisions de l'utilisateur relayées par l'ordonnateur (Q2, Q3, abandon de la partie C).
|
|||
|
|
Journal lu : `D:\Game\LifePlay_Build_5.8_Development_COM23\Windows\LifePlay\Saved\Logs\LifePlay.log`, lu par une copie faite
|
|||
|
|
le 2026-10-21 (2773 lignes, dernière ligne 06:24:31 UTC ; le jeu tournait peut-être encore). Heures du journal en UTC, locale = UTC + 2.
|
|||
|
|
Les numéros de ligne « l. n » du § 1 renvoient à ce journal.
|
|||
|
|
|
|||
|
|
## 1. Objectif mesurable
|
|||
|
|
|
|||
|
|
### 1.1 Essai T0 : ce que dit le journal (établi)
|
|||
|
|
| UTC | Lignes | Événement |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 06:14:39 | 1519, 1581 | assis au meuble `_40`, en C2 (`fnt_new048-f-0-sit`) ; lever à 06:15:01 (l. 1635) |
|
|||
|
|
| 06:15:14 | 1649, 1652 | C3 → C1, **debout** (`C1Hidden=1`) |
|
|||
|
|
| 06:15:28 | 1712, 1722 | assis au meuble `_34` **sans quitter C1** (aucune touche C entre l. 1649 et l. 1738) : fait 1 (H1) |
|
|||
|
|
| 06:16:02 → 06:16:10 | 1737-1746 | cycle C2 → C3 → C1, assis `_34` |
|
|||
|
|
| 06:17:00 → 06:17:15 | 1762-1785 | lever en C1, puis cycle debout |
|
|||
|
|
| 06:17:58 | 1857, 1891 | assis `_39` en C1 ; menu `Scene_C` de 06:18:00 à 06:18:08 (l. 1972, 2088) |
|
|||
|
|
| 06:19:19 → 06:20:16 | 2256-2593 | cycle, assis `_39` ; lever en C1 à 06:21:13 (l. 2612) |
|
|||
|
|
| 06:21:33 | 2716, 2726 | assis `_35`, **en C2** (C2 depuis 06:21:16, l. 2620-2622, `C1Hidden=0`) |
|
|||
|
|
| 06:21:36 → 06:21:40 | 2727-2733 | C3 → C1 assis `_35` ; lacet de contrôle +275 à 06:21:41 (l. 2734) |
|
|||
|
|
| 06:22:09.8 → 06:22:11.2 | 2742-2752 | cycle, **C2 pendant 0,6 s** ; lacet de contrôle **-90** à 06:22:11.9 (l. 2753) |
|
|||
|
|
| 06:22:14.3 → 06:22:15.5 | 2755-2764 | cycle, C2 pendant 0,6 s ; lacet de contrôle **-90** à 06:22:16.2 (l. 2765) |
|
|||
|
|
| 06:22:17 → 06:22:21 | 2766-2770 | lacet -121, -179, -198, -203, -193 (souris vers la gauche) ; tangage jusqu'à -60 |
|
|||
|
|
| 06:24:31 | 2773 | dernière ligne, toujours en C1 assis |
|
|||
|
|
|
|||
|
|
- Les quatre assises du joueur jouent `fnt_new048-f-0-sit` (l. 1519, 1712, 1857, 2716). Aucune animation de repas
|
|||
|
|
(`fnt_new017-f-0-use`, vue le matin) n'apparaît dans la fenêtre T0 (Grep `fnt_|-f-0-` sur la copie du journal).
|
|||
|
|
- Entrées en C1 assis : 5 (06:16:10, 06:20:16, 06:21:40, 06:22:11, 06:22:15). Le récit de l'utilisateur compte deux séries de trois
|
|||
|
|
cycles. **Correspondance estimée** : la série « Sit » est `_35` (06:21:40 mauvaise, 06:22:11 et 06:22:15 bonnes) ; l'autre série
|
|||
|
|
(`_34`, `_39`) n'a que deux entrées dans cette fenêtre. **Inconnu** : la chaise « repas » du récit.
|
|||
|
|
|
|||
|
|
### 1.2 Lecture du code
|
|||
|
|
**Position (établi, DIAG § 3)** : la minuterie `UpdateRoomCam` de Room (0,016 s) pose `FirstPersonSM` au milieu de `lEye` / `rEye`
|
|||
|
|
(`etat/CAM/CAM-B2s-IMPL.md:137`, nœud 512). Ces os sont sous `neckLower`, masqué en C1. Ils sortent donc des os requis et restent figés
|
|||
|
|
(`SkeletalMeshComponent.cpp:2321-2332`). Le fait 1 (06:15:28) le confirme une fois de plus.
|
|||
|
|
|
|||
|
|
**Premier cycle mauvais (fait 2), inconnu** :
|
|||
|
|
- Avant l'entrée de 06:21:40, le cou était démasqué depuis 06:21:16 (`C1Hidden=0`, l. 2620). La voie C1 lit les yeux avant de les masquer
|
|||
|
|
(`CAM-B1-IMPL.md:22`, `:104-107`). H1 seul prédit donc une bonne vue dès ce premier cycle, et le code lu n'explique pas l'échec.
|
|||
|
|
- Pistes estimées :
|
|||
|
|
- (a) la pose assise n'est pas encore finale 7 s après l'assise (transition ou fondu de l'animation de meuble), et le masquage la fige
|
|||
|
|
à mi-course ; les entrées de 06:22:11 et 06:22:15 tombent 38 et 42 s après l'assise ;
|
|||
|
|
- (b) une tête animée en boucle (L1 de `ERR145-SPEC.md` § 1.2) est figée à une phase quelconque.
|
|||
|
|
- Pour la conception qui suit, la question devient sans objet : la position ne dépend plus ni de l'instant du masquage ni des os de la tête.
|
|||
|
|
|
|||
|
|
**Lacet vers la droite (fait 3)** :
|
|||
|
|
- Établi : aucune étape de l'entrée en C1 n'écrit le lacet de contrôle.
|
|||
|
|
- La voie P6 de Room lit ce lacet (`524 GetControlRotation → 514 → 539`) et l'écrit sur `FirstPersonSM` avant `SetViewTargetWithBlend`
|
|||
|
|
(`CAM-B2s-IMPL.md:138`, `:124`). La minuterie fait de même à chaque passage (`:137`).
|
|||
|
|
- Côté C++, `ResetC1PitchOnExit` ne remet que le tangage (`LPCameraModeSubsystem.cpp:811-828`), et `TickC1Pitch` n'écrit que le
|
|||
|
|
tangage (`:787-790`).
|
|||
|
|
- Établi : le lacet de contrôle est écrit ailleurs :
|
|||
|
|
- par `PlaceC2`, à l'entrée en C2 : lacet de l'acteur joueur (`CAM-B2d-IMPL.md:117`) ;
|
|||
|
|
- en C2 et en C1, par `CircleCamera` et `LeftRight`, à la souris X et aux flèches (`etat/CAM/CAM2/CAM2-IMPL-02.md:28`,
|
|||
|
|
`CAM-COMMUN6-DIAG.md:315`).
|
|||
|
|
C3 ne l'écrit pas : la garde de `CircleCamera` exige la vue `CamRoom` ou `CamC1On` (même source). Une entrée en C1 hérite donc du lacet
|
|||
|
|
laissé par C2.
|
|||
|
|
- Établi : assis, l'acteur n'est pas tourné par la marche. `Walk` ne pose le lacet de l'acteur que hors siège
|
|||
|
|
(`LPDirectControlSubsystem.cpp:1545-1547`). Le commentaire `:564` dit « son lacet est celui de la place ». Le joueur n'est pas possédé :
|
|||
|
|
`SetControlRotation` ne tourne pas l'acteur (`CAM-COMMUN3-DIAG.md:137-139`).
|
|||
|
|
- Établi par le journal : aux 2e et 3e entrées, C2 a duré 0,6 s et le lacet vaut exactement -90 (l. 2753, 2765), soit le lacet de l'acteur
|
|||
|
|
posé par `PlaceC2`, sans souris (estimé fort). L'utilisateur tourne ensuite vers la gauche jusqu'à -193 / -203 (l. 2766-2770).
|
|||
|
|
- **Estimé** : l'avant du corps assis sur `_35` est vers -190 ± 15°, soit environ 100° à gauche du lacet de l'acteur. La vue part donc
|
|||
|
|
vers la droite parce que l'animation de siège ne regarde pas dans l'axe de l'acteur.
|
|||
|
|
- **Inconnu** : pourquoi l'animation et l'acteur diffèrent (données de meuble ou d'animation). Le témoin le mesure (`lacet buste` contre
|
|||
|
|
`lacet acteur`, § 2.4).
|
|||
|
|
|
|||
|
|
### 1.3 Décisions de l'utilisateur prises en compte (relayées par l'ordonnateur)
|
|||
|
|
- Q2 : « non : la caméra C1 doit rester stable, sans suivre les mouvements de tête des animations. »
|
|||
|
|
- T0b : une fois la vue bien placée, aucune peau proche n'est visible. L2 est écartée et la partie C (`lp.C1NearClip`, `lp.C1Forward`)
|
|||
|
|
est retirée.
|
|||
|
|
- Q3 : « oui ». La caméra stable sera étendue à la POV de scène dans une fiche à part, après l'IMPL d'ERR-145. Ici, seulement ce qui
|
|||
|
|
sera réutilisable (§ 4.5).
|
|||
|
|
|
|||
|
|
### 1.4 Objectifs (critères chiffrés au § 7)
|
|||
|
|
1. **Position** : en C1, à chaque image, la vue est posée sur une **ancre** : l'os parent de `neckLower` (os visible, donc animé) plus un
|
|||
|
|
décalage fixe dans son repère. La vue ne lit plus les yeux. Témoin `vue-ancre` ≤ 0,5 cm sans lissage, debout, assis, après un lever
|
|||
|
|
et à chaque cycle C, quel que soit l'ordre (entrer en C1 puis s'asseoir, ou l'inverse).
|
|||
|
|
2. **Stabilité** : assis immobile, la vue ne suit aucun mouvement de tête de l'animation (décision Q2). Elle ne bouge qu'avec le buste.
|
|||
|
|
3. **Lacet** : à chaque entrée en C1 assis, et 0,5 s après s'être assis en C1, le lacet de contrôle est aligné sur l'avant du corps.
|
|||
|
|
Témoin `ecart vue-buste` dans ±10° sur la première ligne qui suit ; l'utilisateur regarde droit devant sans toucher la souris.
|
|||
|
|
4. **Rotation** : en C1, la rotation de la vue est celle du contrôleur seul (tangage, lacet), roulis nul ; jamais celle de la tête.
|
|||
|
|
5. **Repli** : `lp.C1Stable 0` à la console rend la vue d'avant dès l'image suivante.
|
|||
|
|
6. **Inchangés** : POV de scène, Scene, C2, C3, Overview, studio, PNJ. Room, PC, GI, Person et tout asset restent intacts.
|
|||
|
|
|
|||
|
|
## 2. Fichiers touchés, ordre
|
|||
|
|
|
|||
|
|
### 2.0 Conception et ordre d'exécution
|
|||
|
|
Le cœur de la conception : le **modificateur de vue** `ULPRoomBoundsCameraModifier`, déjà installé en permanence
|
|||
|
|
(`LPCameraModeSubsystem.cpp:873-881`), est le dernier à écrire la vue. Il la reprend en C1. Room continue d'écrire `FirstPersonSM`,
|
|||
|
|
mais cette écriture n'est plus vue.
|
|||
|
|
- Ordre dans une image, établi :
|
|||
|
|
- le monde d'abord : `GEngine->Tick` (`LaunchEngineLoop.cpp:5859`), puis le ticker du cœur (`:6103`), dans `FEngineLoop::Tick`
|
|||
|
|
(`:5575-6200`). Le sous-système (`FTSTicker`, `LPCameraModeSubsystem.cpp:733-735`) tourne donc **après** le monde de l'image N et
|
|||
|
|
prépare l'image N+1 ;
|
|||
|
|
- dans `UWorld::Tick`, viennent dans l'ordre les groupes d'acteurs (`LevelTick.cpp:1750-1778`), les minuteries, dont `UpdateRoomCam`
|
|||
|
|
de Room (`:1816`), puis les caméras, « after all actors have been ticked » (`:1835-1847`) ;
|
|||
|
|
- dans la caméra, `UpdateViewTarget` appelle `CalcCamera` (vue de `FirstPersonSM`), puis `ApplyCameraModifiers`
|
|||
|
|
(`PlayerCameraManager.cpp:456-463`).
|
|||
|
|
Le modificateur passe donc après la minuterie de Room, dans la même image. Il n'y a ni ordre de tick à établir ni asset à écrire.
|
|||
|
|
- Voies écartées :
|
|||
|
|
- (i) un `FTickableGameObject` (`LevelTick.cpp:1821`, entre les minuteries et les caméras) qui écrirait `FirstPersonSM` : faisable,
|
|||
|
|
mais il écrit un composant partagé avec la POV (`CAM-B1e-CART.md:89`) et la minuterie le réécrit à chaque passage ;
|
|||
|
|
- (ii) un tick de composant en `TG_PostUpdateWork` : il passe après les caméras (`LevelTick.cpp:1877` contre `:1847`), donc avec une
|
|||
|
|
image de retard ;
|
|||
|
|
- (iii) attacher `FirstPersonSM` au buste : Room le détache et le replace à chaque entrée et à chaque passage de minuterie
|
|||
|
|
(`CAM-B1-IMPL.md:22`, `CAM-B2s-IMPL.md:137-138`) ;
|
|||
|
|
- (iv) l'option B du DIAG (Room, MCP) : la fiche demande du 100 % C++.
|
|||
|
|
- Partage des rôles :
|
|||
|
|
- le **sous-système** étalonne le décalage hors C1, lit les variables, choisit les réglages et écrit le témoin ;
|
|||
|
|
- le **modificateur** calcule l'ancre et pose la vue et le lacet dans l'image même.
|
|||
|
|
L'entrée en C1 est vue par le modificateur dès la première image où la cible de vue est le joueur, sans attendre le libellé. Aucune
|
|||
|
|
image n'est donc montrée avec l'ancien lacet.
|
|||
|
|
|
|||
|
|
Ordre des fichiers (tous sous `D:\Game\LifePlay-master 5.8\Source\LifePlay\`) : (1) `LPRoomBoundsCameraModifier.h`,
|
|||
|
|
(2) `LPRoomBoundsCameraModifier.cpp`, (3) `LPCameraModeSubsystem.h`, (4) `LPCameraModeSubsystem.cpp`.
|
|||
|
|
Volume estimé : ≈ +320 / −40 lignes (modificateur ≈ +160, sous-système ≈ +160 dont 50 de témoin qui en remplacent 36). Aucune écriture
|
|||
|
|
d'asset, une compilation.
|
|||
|
|
|
|||
|
|
### 2.1 `LPRoomBoundsCameraModifier.h`
|
|||
|
|
- Commentaire de tête (l. 6-7) : ajouter « ERR145-SPEC-02 : en C1, la vue est posée sur l'ancre du buste et tournée par le contrôleur ».
|
|||
|
|
- Après `#include "Camera/CameraModifier.h"` (l. 12), déclaration avancée `class USkeletalMeshComponent;`. Avant `UCLASS()` (l. 15), une
|
|||
|
|
structure C++ simple, hors réflexion :
|
|||
|
|
```cpp
|
|||
|
|
/** ERR145-SPEC-02 : parametres de la vue C1 stable, poses a chaque image par ULPCameraModeSubsystem (etat de la fin de
|
|||
|
|
* l'image precedente). Player nul = vue C1 stable inactive. */
|
|||
|
|
struct FLPC1StableParams
|
|||
|
|
{
|
|||
|
|
TWeakObjectPtr<AActor> Player;
|
|||
|
|
TWeakObjectPtr<USkeletalMeshComponent> Mesh;
|
|||
|
|
/** Parent de neckLower : os visible (Room ne masque que neckLower), donc requis et anime. */
|
|||
|
|
FName Chest;
|
|||
|
|
/** Milieu des yeux dans le repere du buste (espace composant). */
|
|||
|
|
FVector LocalEye = FVector::ZeroVector;
|
|||
|
|
/** Avant du corps dans le repere du buste ; nul = inconnu (repli sur les clavicules). */
|
|||
|
|
FVector LocalForward = FVector::ZeroVector;
|
|||
|
|
/** Person.OccupyingFnt valide. */
|
|||
|
|
bool bSeated = false;
|
|||
|
|
/** lp.C1AlignYaw : 0 jamais, 1 entree assis et assise en C1, 2 toute entree. */
|
|||
|
|
int32 AlignYaw = 1;
|
|||
|
|
/** lp.C1Smooth, constante de temps en s (0 = aucun lissage). */
|
|||
|
|
float SmoothTime = 0.f;
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
- Section publique, après `SetStudio` (l. 38) :
|
|||
|
|
```cpp
|
|||
|
|
/** ERR145-SPEC-02 : vue C1 stable pour les images suivantes (Player nul = inactive). */
|
|||
|
|
void SetC1Stable(const FLPC1StableParams& InC1);
|
|||
|
|
/** Vrai si la derniere image a pose la vue C1 stable (temoin). */
|
|||
|
|
bool IsC1Applied() const { return bC1Active; }
|
|||
|
|
|
|||
|
|
/** Ancre de tete non lissee, en monde : transformee du buste (espace composant) x LocalEye x transformee du composant.
|
|||
|
|
* Fonctions statiques, sans etat : reprises telles quelles par la future POV stable (ERR145-SPEC-02 § 4.5). */
|
|||
|
|
static FVector ComputeHeadAnchor(const USkeletalMeshComponent* Mesh, FName Chest, const FVector& LocalEye);
|
|||
|
|
/** Lacet de l'avant du corps : repere du buste etalonne, sinon clavicules (lCollar, rCollar). Faux sans source. */
|
|||
|
|
static bool ComputeBodyYaw(const USkeletalMeshComponent* Mesh, FName Chest, const FVector& LocalForward, double& OutYaw, const TCHAR*& OutSource);
|
|||
|
|
/** Milieu des yeux dans le repere du buste, en pose de reference du maillage (repli avant tout etalonnage). */
|
|||
|
|
static bool ComputeRefLocalEye(const USkeletalMeshComponent* Mesh, FName Chest, FVector& OutLocalEye);
|
|||
|
|
```
|
|||
|
|
- Section privée, à la fin :
|
|||
|
|
```cpp
|
|||
|
|
void ApplyC1Stable(float DeltaTime, FMinimalViewInfo& InOutPOV, AActor* Player, USkeletalMeshComponent* Mesh);
|
|||
|
|
FLPC1StableParams C1;
|
|||
|
|
bool bC1Active = false;
|
|||
|
|
bool bC1SeatedWas = false;
|
|||
|
|
/** Delai avant l'alignement qui suit une assise en C1 ; negatif = aucun. */
|
|||
|
|
float C1SeatAlignLeft = -1.f;
|
|||
|
|
/** Ancre lissee, en espace composant (la marche et le deplacement du composant ne creent aucun retard). */
|
|||
|
|
FVector C1SmoothedCS = FVector::ZeroVector;
|
|||
|
|
uint64 C1LastFrame = 0;
|
|||
|
|
FVector C1LastLocation = FVector::ZeroVector;
|
|||
|
|
FRotator C1LastRotation = FRotator::ZeroRotator;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 `LPRoomBoundsCameraModifier.cpp`
|
|||
|
|
- Includes à ajouter : `Components/SkeletalMeshComponent.h`, `GameFramework/PlayerController.h`, `CoreGlobals.h` (`GFrameCounter`,
|
|||
|
|
`CoreGlobals.h:532`), `LifePlay.h` (`LogLifePlay`, `LifePlay.h:9`).
|
|||
|
|
- `ModifyCamera` : insérer après le bloc Studio (l. 42-47), **avant** le filtre des l. 49-54, qui rend `false` pour toute vue non bornée,
|
|||
|
|
C1 comprise :
|
|||
|
|
```cpp
|
|||
|
|
// ERR145-SPEC-02 : vue C1 stable. Cible de vue relue a chaque image : hors de la vue du joueur, rien.
|
|||
|
|
AActor* C1Player = C1.Player.Get();
|
|||
|
|
USkeletalMeshComponent* C1Mesh = C1.Mesh.Get();
|
|||
|
|
if (C1Player && C1Mesh && !C1.Chest.IsNone() && CameraOwner && CameraOwner->GetViewTarget() == C1Player)
|
|||
|
|
{
|
|||
|
|
ApplyC1Stable(DeltaTime, InOutPOV, C1Player, C1Mesh);
|
|||
|
|
}
|
|||
|
|
else if (bC1Active)
|
|||
|
|
{
|
|||
|
|
bC1Active = false;
|
|||
|
|
C1SeatAlignLeft = -1.f;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
- Nouvelles fonctions :
|
|||
|
|
```cpp
|
|||
|
|
void ULPRoomBoundsCameraModifier::SetC1Stable(const FLPC1StableParams& InC1)
|
|||
|
|
{
|
|||
|
|
C1 = InC1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
FVector ULPRoomBoundsCameraModifier::ComputeHeadAnchor(const USkeletalMeshComponent* Mesh, FName Chest, const FVector& LocalEye)
|
|||
|
|
{
|
|||
|
|
const FTransform ChestCS = Mesh->GetSocketTransform(Chest, RTS_Component);
|
|||
|
|
return Mesh->GetComponentTransform().TransformPosition(ChestCS.TransformPosition(LocalEye));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool ULPRoomBoundsCameraModifier::ComputeBodyYaw(const USkeletalMeshComponent* Mesh, FName Chest, const FVector& LocalForward, double& OutYaw, const TCHAR*& OutSource)
|
|||
|
|
{
|
|||
|
|
if (!LocalForward.IsNearlyZero())
|
|||
|
|
{
|
|||
|
|
const FVector Forward = Mesh->GetSocketTransform(Chest, RTS_World).TransformVectorNoScale(LocalForward);
|
|||
|
|
const FVector Flat(Forward.X, Forward.Y, 0.0);
|
|||
|
|
// Buste a plus de ~72 degres de la verticale (couche) : pas de lacet fiable.
|
|||
|
|
if (Flat.SizeSquared() > 0.09)
|
|||
|
|
{
|
|||
|
|
OutYaw = Flat.Rotation().Yaw;
|
|||
|
|
OutSource = TEXT("buste");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
static const FName LeftCollar(TEXT("lCollar"));
|
|||
|
|
static const FName RightCollar(TEXT("rCollar"));
|
|||
|
|
if (Mesh->GetBoneIndex(LeftCollar) != INDEX_NONE && Mesh->GetBoneIndex(RightCollar) != INDEX_NONE)
|
|||
|
|
{
|
|||
|
|
// UE : X avant, Y droite, Z haut ; (droite - gauche) x haut = avant.
|
|||
|
|
const FVector Across = Mesh->GetSocketLocation(RightCollar) - Mesh->GetSocketLocation(LeftCollar);
|
|||
|
|
const FVector Flat = FVector::CrossProduct(Across, FVector::UpVector).GetSafeNormal2D();
|
|||
|
|
if (!Flat.IsNearlyZero())
|
|||
|
|
{
|
|||
|
|
OutYaw = Flat.Rotation().Yaw;
|
|||
|
|
OutSource = TEXT("clavicules");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
OutSource = TEXT("aucune");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool ULPRoomBoundsCameraModifier::ComputeRefLocalEye(const USkeletalMeshComponent* Mesh, FName Chest, FVector& OutLocalEye)
|
|||
|
|
{
|
|||
|
|
// Transformee de reference en espace composant : produit des transformees locales de reference de l'os jusqu'a la racine,
|
|||
|
|
// dans l'ordre enfant puis parent (C = A * B applique A puis B, TransformNonVectorized.h:472-473).
|
|||
|
|
auto RefComponentSpace = [Mesh](FName Bone, FTransform& Out)
|
|||
|
|
{
|
|||
|
|
Out = FTransform::Identity;
|
|||
|
|
for (FName Name = Bone; !Name.IsNone(); Name = Mesh->GetParentBone(Name))
|
|||
|
|
{
|
|||
|
|
const int32 Index = Mesh->GetBoneIndex(Name);
|
|||
|
|
if (Index == INDEX_NONE)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
Out = Out * Mesh->GetRefPoseTransform(Index);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
FTransform ChestRef, LeftRef, RightRef;
|
|||
|
|
if (!RefComponentSpace(Chest, ChestRef) || !RefComponentSpace(FName(TEXT("lEye")), LeftRef) || !RefComponentSpace(FName(TEXT("rEye")), RightRef))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
OutLocalEye = ChestRef.InverseTransformPosition((LeftRef.GetLocation() + RightRef.GetLocation()) * 0.5);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ERR145-SPEC-02 § 2.2 : la vue C1 ne lit plus les yeux (masques sous neckLower, donc figes, ERR145-DIAG § 3.2). Position = ancre
|
|||
|
|
// du buste ; rotation = controleur seul ; lacet aligne sur l'avant du corps a l'entree assis et apres une assise en C1.
|
|||
|
|
void ULPRoomBoundsCameraModifier::ApplyC1Stable(float DeltaTime, FMinimalViewInfo& InOutPOV, AActor* Player, USkeletalMeshComponent* Mesh)
|
|||
|
|
{
|
|||
|
|
// Second appel dans la meme image (fondu de vue : PlayerCameraManager.cpp:903 et :916) : meme resultat, etat inchange.
|
|||
|
|
if (bC1Active && C1LastFrame == GFrameCounter)
|
|||
|
|
{
|
|||
|
|
InOutPOV.Location = C1LastLocation;
|
|||
|
|
InOutPOV.Rotation = C1LastRotation;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const bool bEntry = !bC1Active;
|
|||
|
|
bC1Active = true;
|
|||
|
|
C1LastFrame = GFrameCounter;
|
|||
|
|
|
|||
|
|
// Position : buste (espace composant) x decalage ; lissage dans l'espace composant, saut de plus de 30 cm = recale.
|
|||
|
|
const FVector EyeCS = Mesh->GetSocketTransform(C1.Chest, RTS_Component).TransformPosition(C1.LocalEye);
|
|||
|
|
if (bEntry || C1.SmoothTime <= 0.f || FVector::DistSquared(EyeCS, C1SmoothedCS) > FMath::Square(30.0))
|
|||
|
|
{
|
|||
|
|
C1SmoothedCS = EyeCS;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
C1SmoothedCS = FMath::Lerp(C1SmoothedCS, EyeCS, 1.0 - FMath::Exp(-DeltaTime / C1.SmoothTime));
|
|||
|
|
}
|
|||
|
|
InOutPOV.Location = Mesh->GetComponentTransform().TransformPosition(C1SmoothedCS);
|
|||
|
|
|
|||
|
|
// Lacet : entree en C1 assise (ou toute entree si AlignYaw = 2) ; assise pendant C1 : 0,5 s apres (pose posee).
|
|||
|
|
bool bAlign = bEntry && (C1.AlignYaw >= 2 || (C1.AlignYaw >= 1 && C1.bSeated));
|
|||
|
|
if (bEntry)
|
|||
|
|
{
|
|||
|
|
C1SeatAlignLeft = -1.f;
|
|||
|
|
}
|
|||
|
|
else if (C1.bSeated && !bC1SeatedWas)
|
|||
|
|
{
|
|||
|
|
C1SeatAlignLeft = 0.5f;
|
|||
|
|
}
|
|||
|
|
bC1SeatedWas = C1.bSeated;
|
|||
|
|
if (C1SeatAlignLeft >= 0.f)
|
|||
|
|
{
|
|||
|
|
C1SeatAlignLeft -= DeltaTime;
|
|||
|
|
if (C1SeatAlignLeft < 0.f)
|
|||
|
|
{
|
|||
|
|
bAlign = C1.AlignYaw >= 1 && C1.bSeated;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
APlayerController* PC = CameraOwner->GetOwningPlayerController();
|
|||
|
|
FRotator Control = PC ? PC->GetControlRotation() : InOutPOV.Rotation;
|
|||
|
|
if (PC && bAlign)
|
|||
|
|
{
|
|||
|
|
double BodyYaw = 0.0;
|
|||
|
|
const TCHAR* Source = nullptr;
|
|||
|
|
if (ComputeBodyYaw(Mesh, C1.Chest, C1.LocalForward, BodyYaw, Source))
|
|||
|
|
{
|
|||
|
|
UE_LOG(LogLifePlay, Log, TEXT("C1 stable : lacet de controle %+.0f -> %+.0f (avant du corps, source %s ; %s ; lacet de l'acteur %+.0f)"),
|
|||
|
|
FRotator::NormalizeAxis(Control.Yaw), BodyYaw, Source, bEntry ? TEXT("entree en C1") : TEXT("assise en C1"),
|
|||
|
|
FRotator::NormalizeAxis(Player->GetActorRotation().Yaw));
|
|||
|
|
Control.Yaw = BodyYaw;
|
|||
|
|
PC->SetControlRotation(FRotator(Control.Pitch, Control.Yaw, 0.0));
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
UE_LOG(LogLifePlay, Log, TEXT("C1 stable : lacet non aligne (aucune source pour l'avant du corps)"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Rotation : controleur seul (tangage : TickC1Pitch ; lacet : Room.CircleCamera a la souris), sans roulis.
|
|||
|
|
InOutPOV.Rotation = FRotator(Control.Pitch, Control.Yaw, 0.0);
|
|||
|
|
C1LastLocation = InOutPOV.Location;
|
|||
|
|
C1LastRotation = InOutPOV.Rotation;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Le contrôleur écrit est celui que lit Room. `SetControlRotation` ne tourne pas l'acteur assis : seul `Walk` le tourne, et seulement hors
|
|||
|
|
siège (`LPDirectControlSubsystem.cpp:1545-1547`). Au lever en C1, `Walk` tourne le corps vers ce lacet : continuité voulue.
|
|||
|
|
|
|||
|
|
### 2.3 `LPCameraModeSubsystem.h`
|
|||
|
|
- Déclaration avancée `class USkeletalMeshComponent;` (l. 17-25).
|
|||
|
|
- Remplacer le commentaire du témoin C1 (l. 71) par « Témoin C1 (ERR145-SPEC-02) : une ligne par seconde en C1, vue mesurée contre
|
|||
|
|
l'ancre du buste ».
|
|||
|
|
- Après `float C1Cooldown = 0.f;` (l. 74) :
|
|||
|
|
```cpp
|
|||
|
|
/** ERR145-SPEC-02 : vue C1 stable, a chaque image (TickSwitchKey). Etalonne le decalage yeux-buste hors C1 (joueur debout,
|
|||
|
|
* immobile, cou non masque), puis transmet les parametres au modificateur de vue (ancre, lacet, lissage). */
|
|||
|
|
void TickC1Stable(const FString& FrameLabel, float DeltaTime);
|
|||
|
|
/** Maillage etalonne, son buste (parent de neckLower) et ses decalages (reference, etalonne), avant du corps etalonne. */
|
|||
|
|
TWeakObjectPtr<USkeletalMeshComponent> C1CalMesh;
|
|||
|
|
FName C1Chest;
|
|||
|
|
FVector C1RefEye = FVector::ZeroVector;
|
|||
|
|
bool bC1RefEye = false;
|
|||
|
|
FVector C1CalEye = FVector::ZeroVector;
|
|||
|
|
FVector C1CalForward = FVector::ZeroVector;
|
|||
|
|
int32 C1CalCount = 0;
|
|||
|
|
float C1CalCooldown = 0.f;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 `LPCameraModeSubsystem.cpp`
|
|||
|
|
- Includes : `HAL/IConsoleManager.h` (`TAutoConsoleVariable`, `IConsoleManager.h:2028`), `Engine/SkinnedAsset.h` (nom de l'asset au
|
|||
|
|
journal ; `SkinnedAsset.h:57`).
|
|||
|
|
- Espace `LPCameraModeLocal` :
|
|||
|
|
- ajouter `const FName NeckLowerBone(TEXT("neckLower"));`, `const float C1CalInterval = 0.5f;` et `const float C1CalMaxSpeed = 10.f;` ;
|
|||
|
|
- retirer `C1Interval`, `C1MaxEyeDistance`, `C1MaxPitch`, `C1MaxYawGap` (l. 104, 106-108). Grep : ils ne servent qu'à l'ancien
|
|||
|
|
`WitnessC1` (l. 1203, 1220-1221) ;
|
|||
|
|
- garder `C1LogInterval` (l. 105), `C1PitchMin` / `C1PitchMax` (l. 132-133, utilisés par `TickC1Pitch`), `LeftEyeSocket` /
|
|||
|
|
`RightEyeSocket` (l. 102-103).
|
|||
|
|
- Après la fermeture de l'espace de noms (l. 728) :
|
|||
|
|
```cpp
|
|||
|
|
// ERR145-SPEC-02 : bascules de l'essai en jeu (console, touche ² ou ~, DefaultInput.ini:166-168), sans recompiler.
|
|||
|
|
static TAutoConsoleVariable<int32> CVarLPC1Stable(TEXT("lp.C1Stable"), 1,
|
|||
|
|
TEXT("ERR145 : 1 = vue C1 stable (ancre sur le buste, rotation du controleur) ; 0 = vue d'avant (camera de Room sur les yeux)."));
|
|||
|
|
static TAutoConsoleVariable<int32> CVarLPC1AlignYaw(TEXT("lp.C1AlignYaw"), 1,
|
|||
|
|
TEXT("ERR145 : lacet aligne sur l'avant du corps : 0 = jamais ; 1 = entree en C1 assis et 0,5 s apres une assise en C1 ; 2 = toute entree en C1."));
|
|||
|
|
static TAutoConsoleVariable<float> CVarLPC1Smooth(TEXT("lp.C1Smooth"), 0.f,
|
|||
|
|
TEXT("ERR145 : lissage de la position C1, constante de temps en s (espace du maillage) ; 0 = aucun."));
|
|||
|
|
static TAutoConsoleVariable<int32> CVarLPC1EyeSource(TEXT("lp.C1EyeSource"), 0,
|
|||
|
|
TEXT("ERR145 : decalage yeux-buste : 0 = etalonne debout, sinon pose de reference ; 1 = pose de reference ; 2 = etalonne seulement."));
|
|||
|
|
```
|
|||
|
|
- Nouvelle méthode, après `ResetC1PitchOnExit` (l. 828). Les accès suivent `TickC1Pitch` (l. 800-801) :
|
|||
|
|
```cpp
|
|||
|
|
// ERR145-SPEC-02 § 2.4 : la vue C1 est posee par le modificateur sur le parent de neckLower plus un decalage fixe. Le decalage est
|
|||
|
|
// etalonne hors C1, joueur debout, immobile et rendu, cou non masque (yeux animes) ; a defaut, pose de reference du maillage.
|
|||
|
|
void ULPCameraModeSubsystem::TickC1Stable(const FString& FrameLabel, float DeltaTime)
|
|||
|
|
{
|
|||
|
|
UGameInstance* GI = GetGameInstance();
|
|||
|
|
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;
|
|||
|
|
ACharacter* Player = Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter));
|
|||
|
|
USkeletalMeshComponent* Mesh = IsValid(Player) ? Player->GetMesh() : nullptr;
|
|||
|
|
|
|||
|
|
// Maillage nouveau (chargement, CharGen) : buste, reference et etalonnage repris de zero.
|
|||
|
|
if (Mesh != C1CalMesh.Get())
|
|||
|
|
{
|
|||
|
|
C1CalMesh = Mesh;
|
|||
|
|
C1Chest = Mesh ? Mesh->GetParentBone(LPCameraModeLocal::NeckLowerBone) : NAME_None;
|
|||
|
|
C1CalCount = 0;
|
|||
|
|
C1CalEye = FVector::ZeroVector;
|
|||
|
|
C1CalForward = FVector::ZeroVector;
|
|||
|
|
bC1RefEye = Mesh && !C1Chest.IsNone() && ULPRoomBoundsCameraModifier::ComputeRefLocalEye(Mesh, C1Chest, C1RefEye);
|
|||
|
|
if (Mesh)
|
|||
|
|
{
|
|||
|
|
UE_LOG(LogLifePlay, Log, TEXT("C1 stable : maillage %s, buste %s, decalage de reference %s"), *GetNameSafe(Mesh->GetSkinnedAsset()),
|
|||
|
|
*C1Chest.ToString(), bC1RefEye ? *FString::Printf(TEXT("%.0f cm (%.0f, %.0f, %.0f)"), C1RefEye.Size(), C1RefEye.X, C1RefEye.Y, C1RefEye.Z) : TEXT("absent"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const bool bSexOn = LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn);
|
|||
|
|
const UObject* Room = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
|||
|
|
const bool bSeated = Mesh && IsValid(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerOccupyingFnt));
|
|||
|
|
|
|||
|
|
// Etalonnage : toutes les 0,5 s, moyenne glissante (poids 0,2 apres la premiere mesure).
|
|||
|
|
C1CalCooldown = FMath::Max(0.f, C1CalCooldown - DeltaTime);
|
|||
|
|
if (Mesh && !C1Chest.IsNone() && C1CalCooldown <= 0.f && FrameLabel != LPCameraModeLocal::LabelC1 && !bSexOn && !bSeated
|
|||
|
|
&& Player->GetVelocity().Size2D() < LPCameraModeLocal::C1CalMaxSpeed && Mesh->WasRecentlyRendered()
|
|||
|
|
&& !Mesh->IsBoneHiddenByName(LPCameraModeLocal::NeckLowerBone)
|
|||
|
|
&& Mesh->GetBoneIndex(LPCameraModeLocal::LeftEyeSocket) != INDEX_NONE && Mesh->GetBoneIndex(LPCameraModeLocal::RightEyeSocket) != INDEX_NONE)
|
|||
|
|
{
|
|||
|
|
C1CalCooldown = LPCameraModeLocal::C1CalInterval;
|
|||
|
|
const FTransform ChestCS = Mesh->GetSocketTransform(C1Chest, RTS_Component);
|
|||
|
|
const FVector EyesCS = (Mesh->GetSocketTransform(LPCameraModeLocal::LeftEyeSocket, RTS_Component).GetLocation()
|
|||
|
|
+ Mesh->GetSocketTransform(LPCameraModeLocal::RightEyeSocket, RTS_Component).GetLocation()) * 0.5;
|
|||
|
|
// Debout hors C1, le corps regarde l'avant de l'acteur (orientation vers la marche, LPDirectControlSubsystem.cpp:1538-1544).
|
|||
|
|
const FVector ForwardCS = Mesh->GetComponentTransform().InverseTransformVectorNoScale(Player->GetActorForwardVector());
|
|||
|
|
const double Weight = C1CalCount == 0 ? 1.0 : 0.2;
|
|||
|
|
C1CalEye = FMath::Lerp(C1CalEye, ChestCS.InverseTransformPosition(EyesCS), Weight);
|
|||
|
|
C1CalForward = FMath::Lerp(C1CalForward, ChestCS.InverseTransformVectorNoScale(ForwardCS).GetSafeNormal(), Weight).GetSafeNormal();
|
|||
|
|
++C1CalCount;
|
|||
|
|
if (C1CalCount == 1 || C1CalCount == 20)
|
|||
|
|
{
|
|||
|
|
UE_LOG(LogLifePlay, Log, TEXT("C1 stable : etalonnage %d (decalage yeux-buste %.0f cm, ecart a la reference %.0f cm)"), C1CalCount,
|
|||
|
|
C1CalEye.Size(), bC1RefEye ? FVector::Dist(C1CalEye, C1RefEye) : -1.0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Parametres de l'image suivante ; arme si CamC1On et hors scene (le modificateur verifie en plus la cible de vue).
|
|||
|
|
if (!Modifier)
|
|||
|
|
{
|
|||
|
|
return; // pose par TickViewBounds, appele juste avant dans la meme image (l. 1262, :873-881)
|
|||
|
|
}
|
|||
|
|
const int32 Source = CVarLPC1EyeSource.GetValueOnGameThread();
|
|||
|
|
const bool bUseCal = C1CalCount > 0 && Source != 1;
|
|||
|
|
const bool bUseRef = !bUseCal && bC1RefEye && Source != 2;
|
|||
|
|
FLPC1StableParams Params;
|
|||
|
|
if (Mesh && !C1Chest.IsNone() && (bUseCal || bUseRef) && CVarLPC1Stable.GetValueOnGameThread() != 0 && !bSexOn
|
|||
|
|
&& LPCameraModeLocal::ReadBool(Room, LPCameraModeLocal::RoomCamC1On))
|
|||
|
|
{
|
|||
|
|
Params.Player = Player;
|
|||
|
|
Params.Mesh = Mesh;
|
|||
|
|
Params.Chest = C1Chest;
|
|||
|
|
Params.LocalEye = bUseCal ? C1CalEye : C1RefEye;
|
|||
|
|
Params.LocalForward = C1CalForward;
|
|||
|
|
Params.bSeated = bSeated;
|
|||
|
|
Params.AlignYaw = CVarLPC1AlignYaw.GetValueOnGameThread();
|
|||
|
|
Params.SmoothTime = FMath::Max(0.f, CVarLPC1Smooth.GetValueOnGameThread());
|
|||
|
|
}
|
|||
|
|
Modifier->SetC1Stable(Params);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
- `TickSwitchKey`, après la l. 1264 (`ResetC1PitchOnExit(...)`) : `TickC1Stable(FrameLabel, DeltaTime);`.
|
|||
|
|
- `Deinitialize` : rien à ajouter. `RemoveViewBounds` (l. 744) retire le modificateur avec son état (`:970-979`).
|
|||
|
|
- **Témoin C1** : remplace `WitnessC1` (l. 1196-1231). Appel inchangé (`Tick`, l. 1140-1143, libellé C1).
|
|||
|
|
```cpp
|
|||
|
|
// ERR145-SPEC-02 : une ligne par seconde en C1, sans condition. L'ancre est recalculee ici, que la vue stable soit active ou non :
|
|||
|
|
// avec lp.C1Stable 0, « vue-ancre » chiffre le defaut d'origine (yeux figes) ; avec 1, il vaut 0.
|
|||
|
|
void ULPCameraModeSubsystem::WitnessC1(float DeltaTime)
|
|||
|
|
{
|
|||
|
|
C1Cooldown = FMath::Max(0.f, C1Cooldown - DeltaTime);
|
|||
|
|
if (C1Cooldown > 0.f)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
C1Cooldown = LPCameraModeLocal::C1LogInterval;
|
|||
|
|
UGameInstance* GI = GetGameInstance();
|
|||
|
|
UWorld* World = GI ? GI->GetWorld() : nullptr;
|
|||
|
|
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
|||
|
|
APlayerCameraManager* Manager = PC ? PC->PlayerCameraManager.Get() : nullptr;
|
|||
|
|
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;
|
|||
|
|
if (!Manager || !Mesh || !IsValid(Camera) || Mesh != C1CalMesh.Get() || C1Chest.IsNone())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const bool bCal = C1CalCount > 0 && CVarLPC1EyeSource.GetValueOnGameThread() != 1;
|
|||
|
|
const FVector Anchor = ULPRoomBoundsCameraModifier::ComputeHeadAnchor(Mesh, C1Chest, bCal ? C1CalEye : C1RefEye);
|
|||
|
|
const FVector ChestAt = Mesh->GetSocketLocation(C1Chest);
|
|||
|
|
const FVector Eyes = (Mesh->GetSocketLocation(LPCameraModeLocal::LeftEyeSocket) + Mesh->GetSocketLocation(LPCameraModeLocal::RightEyeSocket)) * 0.5;
|
|||
|
|
const FVector ViewAt = Manager->GetCameraLocation();
|
|||
|
|
const FRotator ViewRotation = Manager->GetCameraRotation();
|
|||
|
|
double BodyYaw = 0.0;
|
|||
|
|
const TCHAR* BodySource = TEXT("aucune");
|
|||
|
|
const bool bBody = ULPRoomBoundsCameraModifier::ComputeBodyYaw(Mesh, C1Chest, C1CalForward, BodyYaw, BodySource);
|
|||
|
|
const ULPRoomBoundsCameraModifier* Modifier = Cast<ULPRoomBoundsCameraModifier>(Manager->FindCameraModifierByClass(ULPRoomBoundsCameraModifier::StaticClass()));
|
|||
|
|
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
|||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Temoin C1 : stable=%d appliquee=%d ; vue-ancre %.1f cm ; yeux-ancre %.0f cm ; camera-yeux %.0f cm ; ancre-buste %.0f cm (haut %+.0f) ; decalage %s (%d mesures, ecart a la reference %.0f cm) ; lacet vue %+.0f controle %+.0f buste %+.0f (%s) acteur %+.0f ; ecart vue-buste %+.0f ; tangage %+.0f ; assis=%d ; cou masque=%d ; rendu=%d ; lissage %.2f s ; minuterie=%d"),
|
|||
|
|
CVarLPC1Stable.GetValueOnGameThread(), (Modifier && Modifier->IsC1Applied()) ? 1 : 0,
|
|||
|
|
FVector::Dist(ViewAt, Anchor), FVector::Dist(Eyes, Anchor), FVector::Dist(Camera->GetComponentLocation(), Eyes),
|
|||
|
|
FVector::Dist(Anchor, ChestAt), Anchor.Z - ChestAt.Z,
|
|||
|
|
bCal ? TEXT("etalonne") : TEXT("reference"), C1CalCount, (C1CalCount > 0 && bC1RefEye) ? FVector::Dist(C1CalEye, C1RefEye) : -1.0,
|
|||
|
|
FRotator::NormalizeAxis(ViewRotation.Yaw), FRotator::NormalizeAxis(PC->GetControlRotation().Yaw), bBody ? BodyYaw : 0.0, BodySource,
|
|||
|
|
FRotator::NormalizeAxis(Player->GetActorRotation().Yaw), bBody ? FRotator::NormalizeAxis(ViewRotation.Yaw - BodyYaw) : 0.0,
|
|||
|
|
FRotator::NormalizeAxis(ViewRotation.Pitch),
|
|||
|
|
IsValid(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerOccupyingFnt)) ? 1 : 0,
|
|||
|
|
Mesh->IsBoneHiddenByName(LPCameraModeLocal::NeckLowerBone) ? 1 : 0, Mesh->WasRecentlyRendered() ? 1 : 0,
|
|||
|
|
CVarLPC1Smooth.GetValueOnGameThread(),
|
|||
|
|
LPCameraModeLocal::TimerActive(IsValid(RoomObject) ? RoomObject : nullptr, LPCameraModeLocal::RoomCamTimer, World) ? 1 : 0);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
La chaîne de format a 22 champs pour 22 arguments (comptés). Le préfixe « Temoin C1 : » est gardé pour les recherches de journal.
|
|||
|
|
- **Plages attendues** (estimées, proportions Genesis 8, non mesurées ; la VAL les cale sur la première ligne debout) :
|
|||
|
|
|
|||
|
|
| Champ | Debout, `stable=1` | Assis, `stable=1` | `stable=0`, debout → C1 → assis (défaut d'origine) |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `appliquee` | 1 | 1 | 0 |
|
|||
|
|
| `vue-ancre` | 0.0 (≤ lissage) | 0.0 | > 10 cm (vue sur les yeux figés) |
|
|||
|
|
| `yeux-ancre` | 0 à 5 cm | > 10 cm possible (yeux figés, sans effet sur la vue) | > 10 cm |
|
|||
|
|
| `camera-yeux` | 0 à 3 cm (Room, minuterie) | idem | idem |
|
|||
|
|
| `ancre-buste` / `haut` | 20 à 35 cm / +15 à +30 | idem (repère du buste) | idem |
|
|||
|
|
| `decalage` | `etalonne`, mesures ≥ 1 ; écart à la référence < 5 cm | idem | idem |
|
|||
|
|
| `ecart vue-buste` | libre (souris) | ±10° à la 1re ligne après une entrée ou une assise | quelconque |
|
|||
|
|
| `lacet acteur` contre `lacet buste` | ≈ égaux | écart à mesurer (≈ 100° estimé sur `_35`) | idem |
|
|||
|
|
| `cou masque` / `rendu` | 1 / 1 | 1 / 1 (0 si le corps sort de l'image) | 1 / 1 |
|
|||
|
|
|
|||
|
|
### 2.5 Retiré par rapport à `ERR145-SPEC.md`
|
|||
|
|
- Option A (`TickC1BoneRefresh`, `lp.C1RefreshBones`, écriture de `VisibilityBasedAnimTickOption`) : non livrée (§ 4.1).
|
|||
|
|
- Partie C (`SetC1View`, `lp.C1NearClip`, `lp.C1Forward`, plan proche par vue, avance de la vue) : retirée (décision T0b).
|
|||
|
|
- `BodyForward` par les clavicules seules : remplacé par `ComputeBodyYaw` (repère du buste étalonné, puis clavicules).
|
|||
|
|
- Le témoin de `ERR145-SPEC.md` § 2.3 est remplacé par celui du § 2.4.
|
|||
|
|
|
|||
|
|
## 3. Repli
|
|||
|
|
- **Sans build** :
|
|||
|
|
- `lp.C1Stable 0` à la console : le sous-système transmet des paramètres vides, et le modificateur cesse d'agir dès l'image suivante
|
|||
|
|
(vue de Room, comportement d'avant) ;
|
|||
|
|
- `lp.C1AlignYaw 0` garde la position stable sans toucher au lacet ;
|
|||
|
|
- `lp.C1EyeSource 1` force la pose de référence si l'étalonnage est mauvais ;
|
|||
|
|
- en Shipping, sans console, les variables gardent leur valeur par défaut.
|
|||
|
|
- **Code** : instantané 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 PC, ni GI, ni Person, ni aucun asset.
|
|||
|
|
|
|||
|
|
## 4. Risques R#, questions Q#
|
|||
|
|
|
|||
|
|
### 4.1 L'option A reste-t-elle utile ? Non, elle devient inutile
|
|||
|
|
- La vue ne lit plus aucun os sous `neckLower`. L'ancre est l'os parent de `neckLower`, qui est visible donc gardé dans les os requis
|
|||
|
|
(`SkeletalMeshComponent.cpp:2321-2332`) et animé. Le gel des os masqués n'a plus d'effet visible : la tête est masquée, et
|
|||
|
|
`FirstPersonSM`, posé par Room sur les yeux figés, n'est plus la vue.
|
|||
|
|
- A n'apporterait plus qu'une chose : le rafraîchissement des os quand le maillage n'est pas rendu (`SkinnedMeshComponent.h:98-101` ;
|
|||
|
|
`ACharacter` est en `AlwaysTickPose`, `Character.cpp:125` cité par le DIAG). Son coût reste entier : deux recalculs des os requis par
|
|||
|
|
passage en C1, R4 de `ERR145-SPEC.md`.
|
|||
|
|
- Recours seulement si T4 montre un saut au retour d'un regard au plafond (`rendu=0`, R2).
|
|||
|
|
|
|||
|
|
### 4.2 Choix de l'ancre
|
|||
|
|
- **Os** : parent de `neckLower`, lu à l'exécution (`GetParentBone`, `SkinnedMeshComponent.h:1263` ; racine → `NAME_None`,
|
|||
|
|
`SkinnedMeshComponent.cpp:2506-2516`). `chestUpper` est attendu sur Genesis 8, mais c'est une **estimation** : la hiérarchie n'a pas été
|
|||
|
|
relue dans les assets. `neckLower` lui-même est exclu, car il est masqué donc figé.
|
|||
|
|
- **Décalage** :
|
|||
|
|
- **étalonné** sur le maillage vivant, hors C1, debout et immobile, cou non masqué, maillage rendu. Il couvre homme, femme et
|
|||
|
|
proportions propres au maillage. Moyenne glissante sur des mesures espacées de 0,5 s ;
|
|||
|
|
- repli sur la **pose de référence** (`GetRefPoseTransform`, locale : `SkinnedMeshComponent.h:2045-2053`, `.cpp:2727-2737`), si la
|
|||
|
|
partie commence directement en C1.
|
|||
|
|
Le témoin écrit l'écart entre les deux.
|
|||
|
|
- **Repère** : la rotation complète du buste. Penché pour manger, le point de vue avance avec le buste ; couché, il prolonge le buste. Un
|
|||
|
|
repère de lacet seul mettrait le point de vue à la verticale d'un buste couché.
|
|||
|
|
- **Lissage** en espace composant : la marche et le déplacement du composant (assise, racine à 77-104 cm du composant selon le DIAG)
|
|||
|
|
ne créent aucun retard. Un saut de plus de 30 cm est recalé.
|
|||
|
|
|
|||
|
|
### 4.3 Risques
|
|||
|
|
- **R1** : l'étalonnage debout fige la tête de l'animation de repos au moment des mesures (regard un peu baissé, tête tournée). Erreur
|
|||
|
|
attendue de quelques cm, moyennée ; `lp.C1EyeSource 1` sert de repli. Témoin : `ecart a la reference`.
|
|||
|
|
- **R2** : en `AlwaysTickPose`, les os ne sont rafraîchis que si le maillage est rendu (`SkinnedMeshComponent.h:100-101`). Assis en C1, le
|
|||
|
|
regard au plafond peut sortir le corps de l'image : le buste reste figé en espace composant. La position de l'acteur reste juste, et
|
|||
|
|
l'erreur est petite au repos (estimé). Témoin : `rendu`. Recours : option A (§ 4.1).
|
|||
|
|
- **R3** : le sous-système arme la vue stable sur l'état de l'image précédente (`LaunchEngineLoop.cpp:5859`, `:6103`). À l'entrée en scène
|
|||
|
|
depuis C1, si la POV vise le joueur dans la même image, l'ancre peut s'appliquer **une image** en POV. Négligeable (estimé). Le
|
|||
|
|
désarmement suit à l'image suivante, au front de `SexOn`.
|
|||
|
|
- **R4** : pendant un fondu de vue, `GetViewTarget()` rend la cible **en attente** (`PlayerCameraManager.cpp:249-256`) et les deux cibles
|
|||
|
|
sont évaluées (`:903`, `:916`). L'ancre s'appliquerait alors aux deux, et le fondu deviendrait une coupe. Établi sans effet
|
|||
|
|
aujourd'hui : C1 est posée sans fondu (`SetViewTargetWithBlend(…, 0, Linear)`, `CAM-B1-IMPL.md:22` ; « fondu 0 partout »,
|
|||
|
|
`CAM-AUDIT.md:44`). Le garde-image (`GFrameCounter`) rend le même résultat au second appel.
|
|||
|
|
- **R5** : `FirstPersonSM` (posé par Room aux yeux figés) n'est plus à la place de la vue. Un Blueprint qui lirait sa position en C1
|
|||
|
|
serait décalé. Côté C++, Grep `FirstPersonSM` sur `Source/LifePlay` ne trouve que des lectures de témoin et la POV (l. 99, 801, 1209,
|
|||
|
|
1643, 1675-1676). Les lecteurs Blueprint ne sont pas relus (incertain). Les clics à l'écran passent par la vue finale du gestionnaire
|
|||
|
|
de caméra, donc par l'ancre (estimé). Essai : T6.
|
|||
|
|
- **R6** : l'alignement 0,5 s après une assise en C1 lit l'avant du corps à ce moment. Une transition plus longue donnerait un angle
|
|||
|
|
faux (piste (a) du § 1.2). La souris corrige ; `lp.C1AlignYaw 0` coupe. Le témoin écrit chaque alignement.
|
|||
|
|
- **R7** : buste couché (plus de 72° de la verticale) : le lacet n'est pas aligné. Le repli sur les clavicules peut alors donner un angle
|
|||
|
|
quelconque (`ComputeBodyYaw`). C1 couché n'a pas été vu en essai : inconnu, à couvrir par T5.
|
|||
|
|
- **R8** : l'étalonnage de l'avant du corps suppose que le maillage debout regarde l'avant de l'acteur (orientation vers la marche,
|
|||
|
|
`LPDirectControlSubsystem.cpp:1538-1544`). Une animation de repos qui tourne le buste fausserait `C1CalForward` (estimé faible).
|
|||
|
|
Le repli clavicules est mesuré à part par le témoin (`buste` contre `clavicules` selon la source).
|
|||
|
|
- **R9** : compilation unitaire. Noms neufs uniques (`CVarLPC1*`, `FLPC1StableParams`) ou membres de classe ; aucun espace anonyme
|
|||
|
|
(`lp-build` § 8).
|
|||
|
|
- **R10** : un `FTSTicker` tourne aussi au menu principal. Sans `PlayerCharacter`, aucun paramètre n'est armé (`Player` nul).
|
|||
|
|
|
|||
|
|
### 4.4 Questions pour l'utilisateur
|
|||
|
|
- **Q4** : faut-il aussi aligner le lacet à l'entrée en C1 **debout** ? Proposition : non (`lp.C1AlignYaw 1`). Debout, l'entrée garde le
|
|||
|
|
lacet de C2, comme aujourd'hui ; `2` est essayé en T6.
|
|||
|
|
- **Q5** : un lissage par défaut ? Proposition : 0 (vue fixée au buste). Valeur retenue après T5 (`lp.C1Smooth 0.15`), puis écrite dans le
|
|||
|
|
code ou dans `[SystemSettings]` de `DefaultEngine.ini` pour la Shipping.
|
|||
|
|
|
|||
|
|
### 4.5 POV de scène (Q3 : oui, fiche à part après l'IMPL d'ERR-145)
|
|||
|
|
- **Effet de cette conception sur la POV** : aucun. La vue stable n'est armée qu'avec `CamC1On` et hors `SexOn` (§ 2.4). Le libellé
|
|||
|
|
« POV » vient de `PC.POVOn` pendant `SexOn` (`LPCameraModeSubsystem.cpp:2620-2625`). Seule exception, une image au plus (R3).
|
|||
|
|
- **Réutilisable tel quel** :
|
|||
|
|
- `ComputeHeadAnchor`, `ComputeBodyYaw` et `ComputeRefLocalEye` : statiques, sans état, valables pour tout maillage de Person ;
|
|||
|
|
- le schéma du modificateur : branche avant le filtre des bornes, cible de vue relue à chaque image, garde-image, lissage en
|
|||
|
|
espace composant ;
|
|||
|
|
- la règle « l'ancre est le parent de l'os masqué » ;
|
|||
|
|
- le modèle de bascule (`lp.POVStable`, par exemple) et le modèle du témoin (`vue-ancre`, `yeux-ancre`, `ancre-buste`, `rendu`).
|
|||
|
|
- **Particularités connues** :
|
|||
|
|
- **Cible de vue** : `SexPersons[SexCameraIndex]` (`CAM-B1e-CART.md:88`), parfois un **PNJ**. L'étalonnage de cette fiche ne couvre
|
|||
|
|
que le joueur. Pour la POV : pose de référence, ou étalonnage par maillage (une table par composant) pris avant la scène.
|
|||
|
|
- **Masquage** : le cou est masqué par **PC** sur `CurrentActor` (`CAM-B1e-CART.md:62-63`). `FirstPersonSM` reste **attaché** à `head`
|
|||
|
|
(`CAM-B1e-CART.md:88-89`). Un os sous l'os masqué donne donc le même gel que H1 : estimé, non observé.
|
|||
|
|
- **Rotation** : en POV, ce n'est pas le contrôleur qui tourne la vue. C'est `FirstPersonSM`, par `AddWorldRotation` côté PC
|
|||
|
|
(`CAM-B1e-CART.md:89`) et par ZQSD côté C++ (`LPCameraModeSubsystem.cpp:1642-1643`). La POV gardera la rotation du composant, ou
|
|||
|
|
la rebasera sur le repère du buste : choix de la fiche POV.
|
|||
|
|
- **Torse** : les poses de scène le bougent beaucoup (couché, à genoux). Le repère complet du buste est donc nécessaire, et le
|
|||
|
|
lissage sans doute plus utile qu'en C1.
|
|||
|
|
- **Entrée en scène** : `ClearC1HiddenOnSexEntry` remet `C1Hidden` à faux (`LPCameraModeSubsystem.cpp:750-764`).
|
|||
|
|
|
|||
|
|
## 5. Interdits
|
|||
|
|
- Aucun changement de Room, PC, GI, Person ni d'asset ; aucun MCP ; aucun changement du plan proche global (`DefaultEngine.ini:50`).
|
|||
|
|
- Ne jamais écrire `VisibilityBasedAnimTickOption`, appeler `RecalcRequiredBones` ou `RecalcRequiredCurves`, ni écrire la position ou
|
|||
|
|
l'attache de `FirstPersonSM` (composant partagé avec la POV) : la vue C1 passe par le modificateur seul.
|
|||
|
|
- Ne toucher à aucun maillage, ni à aucun contrôleur hors du `GetOwningPlayerController()` du gestionnaire de caméra ; aucune écriture sur un PNJ.
|
|||
|
|
- 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).
|
|||
|
|
- Ordre d'une image :
|
|||
|
|
- `Launch/Private/LaunchEngineLoop.cpp:5575` (`FEngineLoop::Tick`), `:5859` (`GEngine->Tick`), `:6103` (`FTSTicker::GetCoreTicker().Tick`,
|
|||
|
|
seul appel de la fonction, après le monde) ;
|
|||
|
|
- `Private/LevelTick.cpp:1750-1778` (groupes d'acteurs), `:1816` (minuteries), `:1821` (`FTickableGameObject::TickObjects`),
|
|||
|
|
`:1835-1847` (caméras « after all actors have been ticked »), `:1877` (`TG_PostUpdateWork`, après les caméras).
|
|||
|
|
- Caméra :
|
|||
|
|
- `Private/PlayerCameraManager.cpp:244-261` : `GetViewTarget` rend la cible en attente pendant un fondu ;
|
|||
|
|
- `:287-307` : `ApplyCameraModifiers`, modificateur sauté s'il est désactivé, et arrêt si `ModifyCamera` rend vrai ;
|
|||
|
|
- `:446-464` : modificateurs appliqués après `UpdateViewTargetInternal`, sauf caméras de débogage ;
|
|||
|
|
- `:881-916` : deux `UpdateViewTarget` pendant un fondu.
|
|||
|
|
- `Classes/Camera/PlayerCameraManager.h:552` (`GetOwningPlayerController`), `:701` (`GetCameraRotation`), `:705` (`GetCameraLocation`).
|
|||
|
|
- `Classes/Camera/CameraModifier.h:51` (`CameraOwner`), `:95` (`ModifyCamera(float, FMinimalViewInfo&)`).
|
|||
|
|
- Contrôleur : `Classes/GameFramework/Controller.h:159` (`GetControlRotation`), `:163` (`SetControlRotation`).
|
|||
|
|
- Maillage :
|
|||
|
|
- `Classes/Components/SkinnedMeshComponent.h` :
|
|||
|
|
- `:98-101` : `AlwaysTickPoseAndRefreshBones` / `AlwaysTickPose` (« Refresh BoneTransforms only when rendered ») ;
|
|||
|
|
- `:1156` : `GetBoneIndex` ; `:1223` : `GetSkinnedAsset` ; `:1263` : `GetParentBone` ;
|
|||
|
|
- `:1360` : `GetSocketTransform(FName, ERelativeTransformSpace)` ; `:1516` : `GetBoneTransform` ;
|
|||
|
|
- `:2036-2053` : `GetRefPosePosition` / `GetRefPoseTransform`, en espace local ;
|
|||
|
|
- `:2171` : `IsBoneHiddenByName`, non const.
|
|||
|
|
- `Private/Components/SkinnedMeshComponent.cpp:2506-2516` : `GetParentBone` rend `NAME_None` à la racine ; `:2727-2737` :
|
|||
|
|
`GetRefPoseTransform` = `GetRefBonePose()[i]`.
|
|||
|
|
- `Private/Components/SkeletalMeshComponent.cpp:2321-2332` : un os visible reste requis. `:2358-2408` : `RecalcRequiredBones` remet la
|
|||
|
|
pose locale à la référence (`:2369`) et ne recalcule l'espace composant que des os requis (DIAG § 3.2).
|
|||
|
|
- `Classes/Components/PrimitiveComponent.h:999` : `WasRecentlyRendered(float Tolerance = 0.2f) const`.
|
|||
|
|
- `Classes/Engine/SkinnedAsset.h:57` : `GetRefSkeleton`.
|
|||
|
|
- Cœur :
|
|||
|
|
- `Core/Public/Math/TransformNonVectorized.h:472-473` : `C = A * B` applique A puis B. `:532`, `:536`, `:539`, `:546` :
|
|||
|
|
`TransformPosition`, `InverseTransformPosition`, `TransformVectorNoScale`, `InverseTransformVectorNoScale` ;
|
|||
|
|
- `Core/Public/CoreGlobals.h:532` : `GFrameCounter` ;
|
|||
|
|
- `Core/Public/HAL/IConsoleManager.h:2028` : `TAutoConsoleVariable(Name, DefaultValue, Help, Flags)`.
|
|||
|
|
- Jeu, lu dans les sources du projet :
|
|||
|
|
- `LPCameraModeSubsystem.cpp` : `:733-735` (tickers), `:787-790` et `:811-828` (tangage), `:873-881` (modificateur toujours
|
|||
|
|
installé), `:970-979` (retrait), `:1196-1231` (ancien témoin), `:1258-1268` (ordre par image), `:2620-2635` (libellés POV et C1) ;
|
|||
|
|
- `LPDirectControlSubsystem.cpp:556-569` (`FirstPersonView`, `OnSeat`) et `:1538-1548` (lacet de l'acteur en C1, hors siège) ;
|
|||
|
|
- `LPRoomBoundsCameraModifier.cpp:40-88`.
|
|||
|
|
|
|||
|
|
## 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, et eux seuls.
|
|||
|
|
2. Grep `VisibilityBasedAnimTickOption|RecalcRequiredBones|PerspectiveNearClipPlane|AddAdditiveOffset|lp\.C1NearClip|lp\.C1Forward|lp\.C1RefreshBones`
|
|||
|
|
sur `Source/LifePlay` → 0. Grep `lp\.C1(Stable|AlignYaw|Smooth|EyeSource)` → 4 définitions, toutes dans `LPCameraModeSubsystem.cpp`.
|
|||
|
|
Grep `SetControlRotation` → les deux d'avant (`LPCameraModeSubsystem.cpp:790`, `:822`) plus une dans `LPRoomBoundsCameraModifier.cpp`.
|
|||
|
|
3. `.\Tools\Build.ps1 -Lot ERR145` (LifePlayEditor) → code 0, module `LifePlay` compilé, 0 C4996 nouveau, « autres lignes : 20 ». Puis une
|
|||
|
|
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 stable'`. Attendu :
|
|||
|
|
- une ligne « C1 stable : maillage … buste chestUpper » au chargement (nom d'os à confirmer) ;
|
|||
|
|
- des lignes « etalonnage 1 » puis « 20 » avant la première entrée en C1 ;
|
|||
|
|
- une ligne « lacet de controle » par entrée assis ;
|
|||
|
|
- des lignes « Temoin C1 » à 1 s d'écart en C1.
|
|||
|
|
|
|||
|
|
### 7.2 Cahier de tests pour l'utilisateur
|
|||
|
|
Essai dans la build ERR145 ; console = touche ². Pour chaque ligne : « vue correcte / non », une capture si non, et trois lignes
|
|||
|
|
« Temoin C1 » recopiées.
|
|||
|
|
- **T1, recette du DIAG § 1** : debout, attendre 5 s hors C1 (étalonnage), puis C1, puis s'asseoir sans quitter C1, sur une chaise « Sit »
|
|||
|
|
puis sur la chaise du repas (`have_a_meal_in`). Regarder autour, en bas, à l'horizontale.
|
|||
|
|
- Attendu : vue à hauteur des yeux du corps assis, corps jamais vu de dehors ; 0,5 s après l'assise, le regard se tourne droit devant.
|
|||
|
|
- Témoin : `stable=1 appliquee=1`, `vue-ancre 0.0`, `assis=1`, une ligne « C1 stable : lacet de controle … (assise en C1) ».
|
|||
|
|
- **T2, preuve par bascule** : même recette avec `lp.C1Stable 0`. Le défaut d'origine revient (vue restée à hauteur debout), et
|
|||
|
|
`vue-ancre` dépasse 10 cm. Puis `lp.C1Stable 1` : la vue revient à l'image suivante, et `vue-ancre` repasse à `0.0`.
|
|||
|
|
- **T3, cycles C assis** (faits 2 et 3) : sur chacune des deux chaises, trois cycles C2 → C3 → C1, dont un avec un long passage en C2 en
|
|||
|
|
tournant la souris.
|
|||
|
|
- Attendu : chaque retour en C1 est bien placé et regarde droit devant, dès le premier cycle.
|
|||
|
|
- Témoin : `ecart vue-buste` dans ±10° sur la première ligne. Noter `lacet acteur` contre `lacet buste`, pour mesurer l'écart.
|
|||
|
|
- **T4, lever, marche, plafond** : assis en C1, regarder au plafond 3 s puis revenir ; se lever (Z) ; marcher en C1 ; s'arrêter.
|
|||
|
|
- Attendu : aucun saut de vue ; caméra aux yeux debout ; le corps se tourne vers le regard au lever.
|
|||
|
|
- Témoin : `assis=0`, `vue-ancre 0.0`. Noter les lignes où `rendu=0`.
|
|||
|
|
- **T5, stabilité, lissage, source** :
|
|||
|
|
- assis 30 s sans souris, sur la chaise du repas : la vue ne suit aucun mouvement de tête ;
|
|||
|
|
- `lp.C1Smooth 0.15`, puis marche en C1 : noter si c'est plus agréable ; remettre à 0 ;
|
|||
|
|
- `lp.C1EyeSource 1`, puis `2` : noter tout écart de hauteur de vue ; remettre à 0 ;
|
|||
|
|
- si un lit le permet en C1, se coucher (R7).
|
|||
|
|
- **T6, non-régression** :
|
|||
|
|
- C2, C3, Overview, studio ;
|
|||
|
|
- scène lancée depuis C1, puis POV sur le joueur et sur le partenaire (comportement d'avant), puis retour en C1 après la scène ;
|
|||
|
|
- clic sur un objet en C1 (interaction juste) ;
|
|||
|
|
- `lp.C1AlignYaw 0` (lacet d'avant), puis `2` (alignement aussi debout, Q4).
|
|||
|
|
|
|||
|
|
### 7.3 Effet attendu sur ERR-106, ERR-111 et ERR-145
|
|||
|
|
- **ERR-145** (`Registre_Erreurs.md:152`) : position corrigée par construction, quel que soit l'ordre et à chaque cycle (T1, T2, T3) ;
|
|||
|
|
lacet recentré à l'entrée assis (T3).
|
|||
|
|
- **ERR-111** (`Registre_Erreurs.md:118`), volet « C1 à côté de la tête après un lever » : corrigé, car la vue ne lit plus les yeux figés
|
|||
|
|
(T4 ; estimé fort). Les volets orientation au retour de scène et sortie de CharGen ne sont pas visés.
|
|||
|
|
- **ERR-106** (`Registre_Erreurs.md:113`), résidu « décalage après un lever » : corrigé pour la même raison (estimé fort).
|
|||
|
|
|
|||
|
|
Fichiers à commiter :
|
|||
|
|
- `A Docs/Migration/etat/REL/ERR145/ERR145-SPEC-02.md` (dépôt du jeu, branche `ue5.8`)
|
|||
|
|
|
|||
|
|
Tours : 27 / 60
|
|||
|
|
|
|||
|
|
## Constats
|
|||
|
|
- Le journal T0 n'a aucune animation de repas pour le joueur (seulement `fnt_new048-f-0-sit`), alors que le récit parle d'une chaise
|
|||
|
|
avec repas. La correspondance entre cycles et chaises est donc estimée (§ 1.1). T1 et T3 nomment les deux chaises.
|
|||
|
|
- À 06:22:11.092 (l. 2748), le libellé « Camera: Studio » apparaît une fois pendant le passage C3 → C1. Sans lien établi avec ERR-145.
|
|||
|
|
- Le premier cycle mauvais (fait 2) n'est pas expliqué par le code lu (§ 1.2). La conception le rend sans objet, et `yeux-ancre` le
|
|||
|
|
chiffrera dans la build ERR145 si on le veut.
|
|||
|
|
- Écart à la fiche : la fiche demandait d'« étudier un décalage calibré (homme / femme) ou une constante ». La SPEC retient un décalage
|
|||
|
|
étalonné **par maillage** à l'exécution, avec repli sur la pose de référence, plutôt que deux constantes. Ainsi aucune valeur n'est
|
|||
|
|
à mesurer à la main, et les proportions propres au maillage sont couvertes.
|
|||
|
|
- Il reste à vérifier par MCP en lecture (hors fiche) : le parent de `neckLower` sur Michael et Bridget, et l'existence de `lCollar` /
|
|||
|
|
`rCollar`. Le journal de la build l'écrira de toute façon (ligne « C1 stable : maillage … buste … »).
|
|||
|
|
- La copie du journal lue est dans le dossier temporaire de la session : elle n'est pas un livrable. Le journal d'origine reste à son chemin.
|