LifePlay-Revival/Docs/Migration/etat/sandbox/S2/S2-IMPL-H.md
edujiro5972 04b053554f Sandbox addon : brancher le jeu sur LifePlay-Sandbox (plugin LPSandbox).
Charge le plugin via AdditionalPluginDirectories, conserve ShowCouple et les commandlets anim côté jeu, et documente les lots S0–S8 (CHG-212 à CHG-220).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 21:10:53 +02:00

176 lines
12 KiB
Markdown

# S2-IMPL-H — Extraction du module `LPAnimMeasure`
- **Date :** 2026-10-06
- **Fiche :** S2-T2 | **Agent :** A-IMPL-H | **Rôle :** Implémentation
- **Modèle prévu :** `claude-opus-5-thinking-high` (M3)
- **Référence suivie :** `S2/S2-CART.md` (décision §1 **(b)**, listes §2 à §5, ordre §8)
- **Statut :** compilé (`LifePlayEditor` **et** `LifePlay`, Win64 Development), **non committé**
- **Hors périmètre respecté :** aucune UI Slate de la sandbox touchée, `LPSandboxConfig.*` et `LPPairSandbox.*` restent dans `LifePlay`.
---
## 1. Fichiers créés
| Fichier | Contenu |
|---|---|
| `Source/LPAnimMeasure/LPAnimMeasure.Build.cs` | `PublicIncludePaths.Add(ModuleDirectory)` (structure plate) ; `PublicDependencyModuleNames = Core, CoreUObject, Engine` ; `PrivateDependencyModuleNames = Json` ; aucune dépendance vers `LifePlay` / `LifePlayEditorTools` |
| `Source/LPAnimMeasure/LPAnimMeasure.h` | `LPANIMMEASURE_API DECLARE_LOG_CATEGORY_EXTERN(LogLPAnimMeasure, Log, All)` |
| `Source/LPAnimMeasure/LPAnimMeasure.cpp` | `IMPLEMENT_MODULE(FDefaultModuleImpl, LPAnimMeasure)` + `DEFINE_LOG_CATEGORY(LogLPAnimMeasure)` |
| `Docs/Migration/etat/sandbox/S2/S2-IMPL-H.md` | cette fiche |
## 2. Fichiers déplacés (`Source/LifePlay/` → `Source/LPAnimMeasure/`)
Les 8 fichiers de `S2-CART.md` §2, déplacés tels quels (pas de `Public/`/`Private/`) :
`LPPoseMeasure.h/.cpp`, `LPSkeletonProfile.h/.cpp`, `LPPoseOrchestrator.h/.cpp`, `LPAnimRedirect.h/.cpp`.
**`git mv` non utilisable** : les 8 fichiers n'étaient pas encore suivis par git (`fatal: not under version control`) — ils apparaissent en `??` dans le statut, comme la plupart des fichiers de la sandbox. Le déplacement est un `Move-Item` ; il n'y a aucun historique git à préserver pour ces fichiers. Les anciens chemins n'existent plus sous `Source/LifePlay/`.
### Modifications appliquées aux fichiers déplacés
| Fichier | Modification |
|---|---|
| `LPPoseMeasure.h` | 6 `LIFEPLAY_API` → `LPANIMMEASURE_API` ; **3 exports ajoutés** (`TFitReport`, `LogReport`, `KeyBones`) ; commentaire d'en-tête du namespace actualisé (`LPANIMMEASURE_API`, mention de la sandbox) |
| `LPPoseMeasure.cpp` | `#include "LifePlay.h"` → `#include "LPAnimMeasure.h"` ; **12** `UE_LOG(LogLifePlay…)` → `LogLPAnimMeasure` |
| `LPSkeletonProfile.h` | 3 `LIFEPLAY_API` → `LPANIMMEASURE_API` (struct `FLPSkeletonProfile`, `Find`, `Identify`) |
| `LPSkeletonProfile.cpp` | `#include "LifePlay.h"` → `#include "LPAnimMeasure.h"` ; **4** `LogLifePlay` → `LogLPAnimMeasure` |
| `LPPoseOrchestrator.h` | **3 exports ajoutés** (`Run`, `Restore`, `SampleAt`) |
| `LPPoseOrchestrator.cpp` | `#include "LifePlay.h"` → `#include "LPAnimMeasure.h"` ; **4** `LogLifePlay` → `LogLPAnimMeasure` |
| `LPAnimRedirect.h` | `LPANIMMEASURE_API` sur `IsGameTarget`/`ClipPath` ; **2 fonctions ajoutées** : `SetUALRoot`, `GetUALRoot` |
| `LPAnimRedirect.cpp` | `#include "LPSandboxConfig.h"` **retiré** ; racine interne `static FString UALRoot()` initialisée à `TEXT("/Game/Quaternius/UAL")` ; `ClipPath` utilise `GetUALRoot()` |
Vérification : plus aucune occurrence de `LIFEPLAY_API`, `LogLifePlay`, `#include "LifePlay.h"` ni `#include "LPSandboxConfig.h"` dans `Source/LPAnimMeasure/` (seules subsistent 3 mentions en commentaire, qui documentent la rupture de couplage).
## 3. Fichiers modifiés hors module
| Fichier | Modification |
|---|---|
| `LifePlay.uproject` | entrée `{ "Name": "LPAnimMeasure", "Type": "Runtime", "LoadingPhase": "Default" }` insérée **avant** `LifePlay` |
| `Source/LifePlay.Target.cs` | `ExtraModuleNames` → `{ "LifePlay", "LPAnimMeasure" }` |
| `Source/LifePlayEditor.Target.cs` | `ExtraModuleNames` → `{ "LifePlay", "LPAnimMeasure", "LifePlayEditorTools" }` |
| `Source/LifePlay/LifePlay.Build.cs` | `PublicDependencyModuleNames.Add("LPAnimMeasure")` |
| `Source/LifePlayEditorTools/LifePlayEditorTools.Build.cs` | `"LPAnimMeasure"` ajouté à `PublicDependencyModuleNames`, à côté de `"LifePlay"` |
| `Source/LifePlay/LifePlay.cpp` | `FDefaultGameModuleImpl` → `FLifePlayModule` (§4) |
| `Source/LifePlay/LPInteractionSandbox.cpp` | `#include "Engine/TextureCube.h"` ajouté — effet de bord de la recomposition des blobs unity (§7) |
| `Tools/LogStats.ps1` | compteur `LogLPAnimMeasure` ajouté à côté de `LogLifePlay` (mitigation **R1** de `S2-CART.md` §7) |
Non touchés : `LPPairSandbox.h/.cpp`, `LPDirectControlSubsystem.h/.cpp`, `LPSandboxConfig.h/.cpp`, les 4 fichiers de `LifePlayEditorTools` consommateurs. Leurs `#include "LPPoseMeasure.h"` etc. restent valides grâce à `PublicIncludePaths.Add(ModuleDirectory)` du nouveau module. `Json` reste déclaré dans `LifePlay.Build.cs` (retrait non traité, sans risque — cf. `S2-CART.md` §5.1).
## 4. Câblage de la racine UAL (`LifePlay.cpp` existe)
`Source/LifePlay/LifePlay.cpp` existait bien (`IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, LifePlay, "LifePlay")`). La solution préférée de `S2-CART.md` §1 a donc été appliquée, sans recourir à l'alternative `BeginPlay` :
```cpp
class FLifePlayModule : public FDefaultGameModuleImpl
{
public:
virtual void StartupModule() override
{
FDefaultGameModuleImpl::StartupModule();
LPAnimRedirect::SetUALRoot(FLPSandboxConfig::Get().QuaterniusUALRoot);
}
};
IMPLEMENT_PRIMARY_GAME_MODULE( FLifePlayModule, LifePlay, "LifePlay" );
```
Conséquences :
- Le flux de configuration va dans le sens `LifePlay` (propriétaire de `LPSandboxConfig`) → `LPAnimMeasure` (setter). Aucun cycle de modules.
- `Config/LPSandbox.ini` (clé `Sandbox.Roots/QuaterniusUALRoot`, contrat `S1-SPEC.md`) est pris en compte pour **tous** les consommateurs — sandbox, `ULPDirectControlSubsystem`, commandlets — sans exiger que la sandbox soit lancée d'abord.
- `SetUALRoot` ignore une chaîne vide : un ini incomplet ne peut pas effacer la racine.
- Défaut interne de `LPAnimRedirect` identique au défaut de `FLPSandboxConfig::QuaterniusUALRoot` (`/Game/Quaternius/UAL`) : si `SetUALRoot` n'est jamais appelé (test unitaire, outil chargeant `LPAnimMeasure` seul), le comportement reste celui d'avant l'extraction.
- `S1-CART.md` doit être mis à jour : `LPAnimRedirect.cpp` n'est plus un consommateur direct de la clé ; `LifePlay.cpp` le devient.
## 5. API publique du module `LPAnimMeasure`
Les structures de données sont des `struct` C++ simples (aucune `UCLASS`/`USTRUCT`, aucune `UFUNCTION`) : header-only, elles ne nécessitent pas d'export, sauf `FLPSkeletonProfile` qui a des méthodes définies en `.cpp`.
### 5.1. `LPAnimMeasure.h`
| Symbole | Export |
|---|---|
| `LogLPAnimMeasure` (catégorie de log) | `LPANIMMEASURE_API` |
### 5.2. `LPAnimRedirect.h`
| Symbole | Export | Statut |
|---|---|---|
| `bool LPAnimRedirect::IsGameTarget()` | `LPANIMMEASURE_API` | export ajouté |
| `FString LPAnimRedirect::ClipPath(const FString& AssetName)` | `LPANIMMEASURE_API` | export ajouté |
| `void LPAnimRedirect::SetUALRoot(const FString& Root)` | `LPANIMMEASURE_API` | **nouveau** |
| `const FString& LPAnimRedirect::GetUALRoot()` | `LPANIMMEASURE_API` | **nouveau** |
### 5.3. `LPPoseMeasure.h`
Types publics non exportés (header-only) : `FLPPose`, `FLPPoseMeasureReport`, `FLPJointScore`, `FLPPoseScore`, `FLPTFitSegment`, `FLPTFitAdjust`, `FLPTFitResult`.
| Symbole | Export | Statut |
|---|---|---|
| `bool Sample(const FString& AnimPath, float Time, FLPPose& Out)` | `LPANIMMEASURE_API` | existant |
| `bool TPoses(const FLPPose& Reference, const FLPPose& Target, FLPPose& OutRefT, FLPPose& OutBodyT)` | `LPANIMMEASURE_API` | existant |
| `const TArray<FString>& TFitReport()` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
| `bool TFitResult(const FLPPose& Reference, const FLPPose& Target, FLPTFitResult& Out)` | `LPANIMMEASURE_API` | existant |
| `bool TransferPose(const FLPPose& Reference, const FLPPose& Template, FLPPose& Out)` | `LPANIMMEASURE_API` | existant |
| `bool WriteRetargetPose(const FLPPose& Reference, const FLPPose& Template, const FString& Path)` | `LPANIMMEASURE_API` | existant |
| `FLPPoseMeasureReport Compare(const TCHAR* Body, const TCHAR* Clip, const FLPPose& Reference, const FLPPose& Target)` | `LPANIMMEASURE_API` | existant |
| `void LogReport(const FLPPoseMeasureReport& Report)` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
| `FName RoleBone(const FLPPose& Pose, const TCHAR* Role, int32 Index = 0)` | `LPANIMMEASURE_API` | existant |
| `TArray<FName> KeyBones(const FLPPose& Pose)` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
Restent internes au module (non exportés) : `AlignToReference`, `ScorePose`, `SyncSample`, `FormatPassage`, `FormatKept`, `FormatSnapshot`.
### 5.4. `LPSkeletonProfile.h`
| Symbole | Export | Statut |
|---|---|---|
| `struct FLPTPoseAdjust` | — (header-only) | existant |
| `struct FLPSkeletonProfile` (+ `Bone`, `Chain`, `Has`) | `LPANIMMEASURE_API` | existant |
| `const FLPSkeletonProfile* Find(const FString& Id)` | `LPANIMMEASURE_API` | existant |
| `const FLPSkeletonProfile* Identify(const TArray<FName>& BoneNames, float* OutScore = nullptr)` | `LPANIMMEASURE_API` | existant |
Reste interne : `LPSkeletonProfile::All()`.
### 5.5. `LPPoseOrchestrator.h`
Type public non exporté (header-only) : `FLPOrchestratorRound`.
| Symbole | Export | Statut |
|---|---|---|
| `FLPOrchestratorRound Run(const TCHAR* Clip, bool bOneSubject = false)` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
| `FLPOrchestratorRound Restore(const TCHAR* Clip)` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
| `FLPOrchestratorRound SampleAt(const TCHAR* Clip, float Fraction)` | `LPANIMMEASURE_API` | **export ajouté** (`LPPairSandbox.cpp`) |
### 5.6. Contrôle de couverture
Tous les symboles `LPPoseMeasure::`, `LPSkeletonProfile::`, `LPPoseOrchestrator::`, `LPAnimRedirect::` référencés depuis `Source/LifePlay/` et `Source/LifePlayEditorTools/` sont exportés (relevé exhaustif : `ClipPath`, `Compare`, `Find`, `IsGameTarget`, `KeyBones`, `LogReport`, `Restore`, `RoleBone`, `Run`, `Sample`, `SampleAt`, `SetUALRoot`, `TFitReport`, `TFitResult`, `TPoses`, `TransferPose`, `WriteRetargetPose`). Mitigation **R3** de `S2-CART.md` §7 couverte.
## 6. Graphe de dépendances obtenu
```
LifePlayEditorTools ──► LifePlay ──► LPAnimMeasure
└──────────────────────────────┘ (public, pour FLPPose dans LPAnimChecks.h)
LPAnimMeasure ──► Core, CoreUObject, Engine (public), Json (privé)
LPAnimMeasure ──► LifePlay : AUCUNE (acyclique)
```
Ordre de chargement `LPAnimMeasure` avant `LifePlay` garanti deux fois : dépendance `Build.cs` et position dans `LifePlay.uproject`.
## 7. Validation
| Target | Résultat | Journal |
|---|---|---|
| `LifePlayEditor Win64 Development` | **succès** — `UnrealEditor-LPAnimMeasure.lib/.dll` produits, `LifePlay` et `LifePlayEditorTools` relinkés sans `LNK2019` | `Saved/Logs/portage/S2-T2-build.log` |
| `LifePlay Win64 Development` (Game) | **succès** après le correctif ci-dessous | `Saved/Logs/portage/S2-T2-build-game.log` |
### Effet de bord rencontré (famille du risque **R5**)
Première passe du target Game : `error C2027: utilisation du type non défini 'UTextureCube'` dans `LPInteractionSandbox.cpp:123` (`LoadObject<UTextureCube>`). Ce fichier n'a jamais inclus `Engine/TextureCube.h` ; il dépendait de l'include transitif apporté par un voisin du **même blob unity** de `LifePlay` — très probablement `LPPairSandbox.cpp`, qui inclut `Engine/TextureCube.h` explicitement. La sortie des 8 fichiers du module a changé la composition des blobs et découvert ce défaut IWYU préexistant (le target Editor, dont le découpage unity diffère, ne le voyait pas).
Correctif : ajout de `#include "Engine/TextureCube.h"` dans `LPInteractionSandbox.cpp`. Aucune collision de namespace anonyme n'est survenue dans `LPAnimMeasure` ; `bUseUnity = false` n'a **pas** été nécessaire.
## 8. Reste à faire
- Validation fonctionnelle (commandlets `LPAnimEval`, `LPAnimCatalog`, `LPAnimTwist`, sandbox) : fiche S2-T3 / `S2-VAL.md`.
- Mise à jour de `S1-CART.md` (ligne `LPAnimRedirect.cpp:23` → consommation via `LifePlay.cpp` / `SetUALRoot`).
- Aucun commit effectué (consigne de la fiche).