# 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& 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 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& 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`). 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).