# S2-CART — Cartographie d'extraction du module `LPAnimMeasure` - **Date :** 2026-10-06 - **Fiche :** S2-T1 | **Agent :** A-CART | **Rôle :** Cartographie (lecture seule, aucun code modifié) - **Modèle prévu :** `claude-sonnet-5-5-high` (M2) - **Références :** `S1/S1-SPEC.md`, `S1/S1-CART.md` --- ## 1. Décision principale : `LPAnimRedirect` `LPAnimRedirect.cpp` appelle `FLPSandboxConfig::Get().QuaterniusUALRoot` (seule dépendance, ligne 23 de `ClipPath`). `LPPoseOrchestrator.cpp` appelle `LPAnimRedirect::ClipPath` 8 fois (l. 223, 233, 237, 255, 262, 303, 345, 404). `LPDirectControlSubsystem.cpp` (runtime jeu, module `LifePlay`) appelle `IsGameTarget()` (l. 880) et `ClipPath()` (l. 960). Garder Redirect dans `LifePlay` n'est **pas possible** : `LPPoseOrchestrator` va dans `LPAnimMeasure`, qui ne peut pas dépendre de `LifePlay` (cycle `LifePlay` → `LPAnimMeasure` → `LifePlay`). ### Choix retenu : **(b) Redirect déménage dans `LPAnimMeasure`, racine UAL injectée** - `LPAnimRedirect` ne dépend plus de `LPSandboxConfig` (le `#include "LPSandboxConfig.h"` disparaît du `.cpp`). - Ajout d'un point d'injection dans `LPAnimRedirect.h` : - `LPANIMMEASURE_API void SetUALRoot(const FString& Root);` - `LPANIMMEASURE_API const FString& GetUALRoot();` - Valeur par défaut interne : `TEXT("/Game/Quaternius/UAL")` (identique au défaut de `FLPSandboxConfig::QuaterniusUALRoot`, donc comportement inchangé sans ini). - `LPANIMMEASURE_API` ajouté à `IsGameTarget()` et `ClipPath()`. - **Câblage côté `LifePlay`** (propriétaire de `LPSandboxConfig`, qui reste dans `LifePlay` pour S3) : `LifePlay.cpp` passe de `FDefaultGameModuleImpl` à une classe `FLifePlayModule : FDefaultGameModuleImpl` dont `StartupModule()` appelle `LPAnimRedirect::SetUALRoot(FLPSandboxConfig::Get().QuaterniusUALRoot)`. Le singleton lit `Config/LPSandbox.ini` ; l'ini est donc pris en compte pour tous les consommateurs (sandbox, `LPDirectControlSubsystem`, commandlets), sans exiger que la sandbox soit lancée d'abord. Alternative plus légère, moins sûre : appeler `SetUALRoot` au début de `ALPPairSandboxCharacter::BeginPlay`. À éviter : `LPDirectControlSubsystem` et les commandlets s'exécuteraient avec la valeur par défaut seulement. - **Couplage à documenter** : le contrat `S1-SPEC.md` (clé `Sandbox.Roots/QuaterniusUALRoot`) reste valide, mais sa consommation passe par `SetUALRoot` au lieu d'un appel direct. `S1-CART.md` doit mettre à jour la ligne `LPAnimRedirect.cpp:23` (consommateur indirect). - Pourquoi (b) plutôt que laisser Redirect hors module : `LPPoseOrchestrator` (dans `LPAnimMeasure`) en dépend directement, donc l'un des deux doit bouger ou être injecté. Déplacer Redirect + une seule fonction d'injection est le plus petit changement (2 fichiers, 1 setter). - Alternative écartée (c) : injecter un resolver `TFunction` dans l'Orchestrator. Plus invasif (signatures `Run`/`Restore`/`SampleAt`), et `IsGameTarget()` devrait rester séparé. --- ## 2. Liste exhaustive des fichiers à déplacer Source : `Source/LifePlay/` → destination : `Source/LPAnimMeasure/` (structure plate comme `LifePlay`, pas de `Public/`/`Private/`). | # | Fichier | Destination | |---|---|---| | 1 | `LPPoseMeasure.h` | `Source/LPAnimMeasure/LPPoseMeasure.h` | | 2 | `LPPoseMeasure.cpp` | `Source/LPAnimMeasure/LPPoseMeasure.cpp` | | 3 | `LPSkeletonProfile.h` | `Source/LPAnimMeasure/LPSkeletonProfile.h` | | 4 | `LPSkeletonProfile.cpp` | `Source/LPAnimMeasure/LPSkeletonProfile.cpp` | | 5 | `LPPoseOrchestrator.h` | `Source/LPAnimMeasure/LPPoseOrchestrator.h` | | 6 | `LPPoseOrchestrator.cpp` | `Source/LPAnimMeasure/LPPoseOrchestrator.cpp` | | 7 | `LPAnimRedirect.h` | `Source/LPAnimMeasure/LPAnimRedirect.h` | | 8 | `LPAnimRedirect.cpp` | `Source/LPAnimMeasure/LPAnimRedirect.cpp` | Nouveaux fichiers du module : | Fichier | Contenu | |---|---| | `Source/LPAnimMeasure/LPAnimMeasure.Build.cs` | règles du module (section 5) | | `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)` | **Ne bougent PAS** : `LPSandboxConfig.h/.cpp` (reste dans `LifePlay`, S3), `LPPairSandbox.*`, `LPDirectControlSubsystem.*`, `LifePlay.h/.cpp`. --- ## 3. Dépendances sortantes de chaque fichier déplacé | Fichier | `#include` projet (hors Engine) | Autres dépendances à traiter | |---|---|---| | `LPPoseMeasure.h` | `CoreMinimal.h` | 6 symboles `LIFEPLAY_API` → `LPANIMMEASURE_API` ; commentaire l.~74 (« LifePlayEditorTools ») | | `LPPoseMeasure.cpp` | `LPPoseMeasure.h`, `LPSkeletonProfile.h`, **`LifePlay.h`** (l. 6) | 12 `UE_LOG(LogLifePlay…)` (l. 298, 862, 878…) ; Engine : `Animation/AnimSequence.h`, `Animation/Skeleton.h`, `Misc/FileHelper.h` | | `LPSkeletonProfile.h` | `CoreMinimal.h` | `FLPSkeletonProfile` et 2 fonctions `LIFEPLAY_API` → `LPANIMMEASURE_API` | | `LPSkeletonProfile.cpp` | `LPSkeletonProfile.h`, **`LifePlay.h`** (l. 5) | 4 `UE_LOG(LogLifePlay…)` ; Engine : `Dom/JsonObject.h`, `Serialization/Json*`, `HAL/FileManager.h`, `Misc/Paths.h` ; lit `Tools/anim/profiles/*.json` via `FPaths::ProjectDir()` (aucun changement) | | `LPPoseOrchestrator.h` | `LPPoseMeasure.h` | aucune API exportée aujourd'hui (voir §4) | | `LPPoseOrchestrator.cpp` | `LPPoseOrchestrator.h`, `LPAnimRedirect.h`, **`LifePlay.h`** (l. 5) | 4 `UE_LOG(LogLifePlay…)` ; Engine : `Animation/AnimSequence.h`, `HAL/FileManager.h`, `Misc/FileHelper.h`, `Misc/Paths.h` | | `LPAnimRedirect.h` | `CoreMinimal.h` | ajouter `LPANIMMEASURE_API` + `SetUALRoot/GetUALRoot` | | `LPAnimRedirect.cpp` | `LPAnimRedirect.h`, **`LPSandboxConfig.h`** (l. 2, à supprimer) | `GConfig` (`Misc/ConfigCacheIni.h`), `FPaths` | ### Couplages à casser (sinon cycle de modules) 1. **`LifePlay.h` / `LogLifePlay`** : 3 `.cpp` (20 appels `UE_LOG` au total : Measure 12, Orchestrator 4, SkeletonProfile 4) incluent `LifePlay.h`. `LPAnimMeasure` ne peut pas dépendre de `LifePlay`. Solution : catégorie propre `LogLPAnimMeasure` (`LPAnimMeasure.h/.cpp`), remplacement mécanique `LogLifePlay` → `LogLPAnimMeasure` et `#include "LifePlay.h"` → `#include "LPAnimMeasure.h"`. - **Effet de bord** : `Tools/LogStats.ps1` ligne 51 compte les lignes `LogLifePlay` (statistique de session). Les lignes `POSET`/`PROFIL` migrent vers `LogLPAnimMeasure` ; ajouter une entrée `LogLPAnimMeasure` dans ce script (ou accepter la baisse du compteur). Aucune occurrence de `LogLifePlay` dans `Source/LifePlayEditorTools`. 2. **`LPSandboxConfig.h`** dans `LPAnimRedirect.cpp` : voir §1. 3. Aucune autre référence à des types `ALP*`/`ULP*`/`FLPSandbox*`/`FLPPair*`/`GEngine`/`UWorld` dans `LPPoseMeasure.cpp`, `LPSkeletonProfile.cpp`, `LPPoseOrchestrator.cpp` (vérifié par grep, 0 occurrence) : l'extraction est propre hors les deux points ci-dessus. --- ## 4. Consommateurs : `#include` et usages (hors fichiers déplacés) ### 4.1. `Source/LifePlay` | Consommateur | `#include` | Symboles utilisés | Action | |---|---|---|---| | `LPPairSandbox.cpp` l. 5-7 | `LPAnimRedirect.h`, `LPPoseMeasure.h`, `LPPoseOrchestrator.h` | `LPAnimRedirect::ClipPath` (l. 1923, 1929, 2011, 3089, 3111) ; `LPPoseMeasure::Sample/Compare/KeyBones/LogReport/TFitReport/TransferPose/WriteRetargetPose/TPoses/TFitResult` (l. 1419-1520, 1923-1932) ; `LPPoseOrchestrator::Run/Restore/SampleAt` (l. 1450-1451) | includes inchangés (include path public du module), mais **exports à ajouter** (§4.3) | | `LPPairSandbox.cpp` l. 8 | `LPSandboxConfig.h` | — | inchangé (reste dans `LifePlay`) | | `LPDirectControlSubsystem.cpp` l. 10 | `LPAnimRedirect.h` | `IsGameTarget()` l. 880, `ClipPath()` l. 960 | include inchangé ; dépendance `LifePlay` → `LPAnimMeasure` en `Build.cs` | | `LifePlay.cpp` | (nouveau) `LPAnimRedirect.h`, `LPSandboxConfig.h` | `SetUALRoot` dans `StartupModule` | à créer | ### 4.2. `Source/LifePlayEditorTools` | Consommateur | `#include` | Symboles utilisés (tous déjà exportés) | |---|---|---| | `LPAnimTwistCommandlet.cpp` l. 10-11 | `LPPoseMeasure.h`, `LPSkeletonProfile.h` | `LPSkeletonProfile::Find` (l. 59), `LPPoseMeasure::Sample`, `TPoses` (l. 265-295) | | `LPAnimEvalCommandlet.cpp` l. 9-10 | `LPPoseMeasure.h`, `LPSkeletonProfile.h` | `LPSkeletonProfile::Find` (l. 86, 171), `Sample`, `TransferPose`, `Compare`, `TPoses`, `WriteRetargetPose`, `TFitResult` | | `LPAnimCatalogCommandlet.cpp` l. 8 | `LPPoseMeasure.h` | `RoleBone` (l. 31), `Sample` (l. 144) | | `LPAnimChecks.h` l. 15 | `LPPoseMeasure.h` | `FLPPose` (type en signature publique) ; `LPAnimChecks.cpp` : `RoleBone` (l. 26), `Sample` (l. 270) | | `LPAnimTwistCommandlet.h` l. 7 | — (commentaire) | mention `LPPoseMeasure::TransferPose` | `LifePlayEditorTools` n'inclut ni `LPPoseOrchestrator.h`, ni `LPAnimRedirect.h`, ni `LPSandboxConfig.h`. `LPAnimEvalCommandlet` a son propre `ClipPath(ClipDir, ...)` (fonction locale) et n'utilise pas `LPAnimRedirect`. ### 4.3. Exports requis (la frontière de module les rend obligatoires) Aujourd'hui exportés par `LIFEPLAY_API` (→ `LPANIMMEASURE_API`) : `LPPoseMeasure::{Sample, TPoses, TFitResult, TransferPose, WriteRetargetPose, Compare, RoleBone}`, `FLPSkeletonProfile` (struct), `LPSkeletonProfile::{Find, Identify}`. **À ajouter** (utilisés aujourd'hui par `LPPairSandbox.cpp` qui reste dans `LifePlay`, donc cross-module après extraction) : | Symbole | Utilisé par | |---|---| | `LPPoseMeasure::KeyBones` | `LPPairSandbox.cpp` l. 1419 | | `LPPoseMeasure::LogReport` | `LPPairSandbox.cpp` l. 1477, 1483, 1493 | | `LPPoseMeasure::TFitReport` | `LPPairSandbox.cpp` l. 1520 | | `LPPoseOrchestrator::Run` | `LPPairSandbox.cpp` l. 1451 | | `LPPoseOrchestrator::Restore` | `LPPairSandbox.cpp` l. 1451 | | `LPPoseOrchestrator::SampleAt` | `LPPairSandbox.cpp` l. 1450 | | `LPAnimRedirect::IsGameTarget`, `ClipPath`, `SetUALRoot`, `GetUALRoot` | `LPPairSandbox.cpp`, `LPDirectControlSubsystem.cpp`, `LifePlay.cpp` | Restent internes (non exportés, usage intra-module uniquement) : `LPPoseMeasure::{AlignToReference, ScorePose, SyncSample, FormatPassage, FormatKept, FormatSnapshot}` et `LPSkeletonProfile::All`. Les structs de données (`FLPPose`, `FLPPoseMeasureReport`, `FLPPoseScore`, `FLPJointScore`, `FLPTFitSegment`, `FLPTFitAdjust`, `FLPTFitResult`, `FLPOrchestratorRound`, `FLPTPoseAdjust`) sont des structs sans UCLASS/USTRUCT ; elles n'ont besoin d'aucun export (types header-only, membres `TArray`/`FString` inline). `FLPSkeletonProfile` conserve son `API` car elle a des méthodes `.cpp` (`Bone`, `Chain`). --- ## 5. Frontière de module proposée ### 5.1. `Source/LPAnimMeasure/LPAnimMeasure.Build.cs` ```csharp using UnrealBuildTool; public class LPAnimMeasure : ModuleRules { public LPAnimMeasure(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; // Structure plate (comme LifePlay) : exposer le dossier du module aux dependants. PublicIncludePaths.Add(ModuleDirectory); PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" }); // LPSkeletonProfile : lecture des profils Tools/anim/profiles/*.json PrivateDependencyModuleNames.Add("Json"); } } ``` Justification des dépendances (vérifiées par les `#include` Engine des 8 fichiers) : - `Core`, `CoreUObject`, `Engine` : `UAnimSequence`, `USkeleton`, `FTransform`, `GConfig`, `FPaths`, `IFileManager`, `FFileHelper`. `Engine` public car `FLPPose` n'expose que des types Core (`FName`, `FTransform`, `FQuat`, `FString`), mais les en-têtes sont consommés par des modules qui ont déjà `Engine` ; le garder public est inoffensif. - `Json` (+ `JsonUtilities` non requis) : `Dom/JsonObject.h`, `Serialization/JsonReader.h`, `JsonSerializer.h` dans `LPSkeletonProfile.cpp`. - **Aucune** dépendance vers `LifePlay` ni `LifePlayEditorTools`. - `Json` reste aussi dans `LifePlay.Build.cs` : il est ajouté en AP1 pour `LPSkeletonProfile` ; une fois le fichier déplacé, vérifier qu'aucun autre fichier de `LifePlay` n'utilise `Json` avant de le retirer (non traité ici, laisser en l'état est sans risque). ### 5.2. Export API `LPANIMMEASURE_API` (généré par UBT pour le module `LPAnimMeasure`) remplace `LIFEPLAY_API` dans les 4 headers déplacés (pas de `LIFEPLAY_API` résiduel dans `LPPoseMeasure.h`, `LPSkeletonProfile.h`, `LPAnimRedirect.h`, `LPPoseOrchestrator.h`). ### 5.3. `Source/LPAnimMeasure/LPAnimMeasure.cpp` ```cpp #include "LPAnimMeasure.h" #include "Modules/ModuleManager.h" IMPLEMENT_MODULE(FDefaultModuleImpl, LPAnimMeasure); DEFINE_LOG_CATEGORY(LogLPAnimMeasure); ``` (`IMPLEMENT_PRIMARY_GAME_MODULE` reste uniquement dans `LifePlay.cpp`.) ### 5.4. `LifePlay.Build.cs` Ajouter : ```csharp PublicDependencyModuleNames.Add("LPAnimMeasure"); ``` Public (et non Private) car `LifePlayEditorTools` dépend de `LifePlay` publiquement et les headers `LPPoseMeasure.h` etc. sont inclus par ses commandlets ; une dépendance explicite dans `LifePlayEditorTools.Build.cs` est quand même ajoutée (5.5) pour ne pas dépendre de la transitivité. `PublicIncludePaths.Add(ModuleDirectory)` de `LifePlay` n'est plus nécessaire pour ces 4 headers mais reste requis pour `LPAssetRegistryHelpers.h` et autres. ### 5.5. `LifePlayEditorTools.Build.cs` Ajouter dans `PublicDependencyModuleNames` (à côté de `"LifePlay"`) : `"LPAnimMeasure"` — public car `LPAnimChecks.h` expose `FLPPose` dans sa signature. ### 5.6. `LifePlay.uproject` — entrée `Modules` Insérer **avant** `LifePlay` (même phase de chargement `Default`, ordre explicite) : ```json { "Name": "LPAnimMeasure", "Type": "Runtime", "LoadingPhase": "Default" } ``` `Type: Runtime` car `LifePlay` (module runtime du jeu packagé) en dépend (`LPDirectControlSubsystem` utilise `LPAnimRedirect`). Les dépendances ne sont pas déclarées dans le `.uproject` (c'est `Build.cs` qui gère ; `AdditionalDependencies` n'est pas requis). ### 5.7. Targets - `Source/LifePlay.Target.cs` : `ExtraModuleNames` → `{ "LifePlay", "LPAnimMeasure" }`. - `Source/LifePlayEditor.Target.cs` : `ExtraModuleNames` → `{ "LifePlay", "LPAnimMeasure", "LifePlayEditorTools" }`. --- ## 6. Graphe de dépendances ### Avant ``` LifePlayEditorTools ──► LifePlay ├─ LPPoseMeasure ──► LPSkeletonProfile ├─ LPPoseOrchestrator ──► LPPoseMeasure, LPAnimRedirect ├─ LPAnimRedirect ──► LPSandboxConfig ├─ LPPairSandbox ──► Redirect, Measure, Orchestrator, SandboxConfig └─ LPDirectControlSubsystem ──► LPAnimRedirect ``` ### Après ``` ┌────────────────────────────────────────────┐ LifePlayEditorTools ──┤ ▼ │ │ LPAnimMeasure (Runtime, nouveau) ▼ │ ├─ LPPoseMeasure ──► LPSkeletonProfile LifePlay ────────┘ ├─ LPPoseOrchestrator ──► LPPoseMeasure │ │ └─► LPAnimRedirect ├─ LPSandboxConfig (reste, S3) ├─ LPAnimRedirect (UALRoot injectee) ├─ LPPairSandbox ───► LPAnimMeasure (Redirect, Measure, Orchestrator) + LPSandboxConfig ├─ LPDirectControlSubsystem ───► LPAnimMeasure (Redirect) └─ LifePlay.cpp StartupModule ─► LPAnimRedirect::SetUALRoot(FLPSandboxConfig::Get().QuaterniusUALRoot) ``` Sens des flèches module : | De | Vers | Type | |---|---|---| | `LifePlay` | `LPAnimMeasure` | Public | | `LifePlayEditorTools` | `LifePlay` | Public (existant) | | `LifePlayEditorTools` | `LPAnimMeasure` | Public (nouveau) | | `LPAnimMeasure` | `LifePlay` | **aucune** (acyclique) | | `LPAnimMeasure` | `LifePlayEditorTools` | **aucune** | Couplage résiduel documenté : `LPAnimRedirect` ne lit plus `LPSandboxConfig` ; le flux va dans le sens `LifePlay` (config) → `LPAnimMeasure` (setter). Ordre de chargement : `LPAnimMeasure` avant `LifePlay` (garanti par la dépendance `Build.cs` et par l'ordre dans `.uproject`). --- ## 7. Risques et points de vigilance | # | Risque | Mitigation | |---|---|---| | R1 | `LogLifePlay` → `LogLPAnimMeasure` : la catégorie change dans les logs `POSET`/`PROFIL` et le compteur `Tools/LogStats.ps1:51` | ajouter `LogLPAnimMeasure` au script | | R2 | `SetUALRoot` non appelé avant premier `ClipPath` (commandlet sans module `LifePlay` initialisé) | `LifePlay` est toujours chargé (module primaire) avant les commandlets de `LifePlayEditorTools` ; défaut interne identique au défaut de la config | | R3 | Symbole non exporté oublié → erreur de lien `LNK2019` dans `LPPairSandbox.cpp` | liste §4.3 (8 symboles existants à exporter + 2 nouveaux) | | R4 | `LifePlayEditorTools` en `bUseUnity = false` : aucune modification | — | | R5 | `LPAnimMeasure` en compilation unity : aucun namespace anonyme avec noms identiques entre les 3 `.cpp` ne doit entrer en collision (ces fichiers étaient déjà dans l'unity de `LifePlay`) | si collision, ajouter `bUseUnity = false` au `Build.cs` | | R6 | Les `.uasset` ne référencent pas ces classes C++ (aucune `UCLASS`/`USTRUCT` dans les 8 fichiers) : pas de redirecteur de classe `CoreRedirects` à écrire | — | | R7 | `Tools/RetargetQuaternius.py` cite `LPPoseMeasure::WriteRetargetPose`/`LPSkeletonProfile::Identify` uniquement en commentaires | aucune action | --- ## 8. Ordre d'exécution suggéré (pour S2-T2) 1. Créer `Source/LPAnimMeasure/` + `LPAnimMeasure.Build.cs`, `LPAnimMeasure.h/.cpp`. 2. `git mv` des 8 fichiers (conserve l'historique). 3. Remplacer `LIFEPLAY_API` → `LPANIMMEASURE_API`, `LifePlay.h` → `LPAnimMeasure.h`, `LogLifePlay` → `LogLPAnimMeasure` dans les fichiers déplacés ; ajouter les exports manquants (�4.3) ; retirer l'include de `LPSandboxConfig.h` dans `LPAnimRedirect.cpp` et ajouter `SetUALRoot/GetUALRoot`. 4. `LifePlay.cpp` : `FLifePlayModule::StartupModule` → `SetUALRoot(...)`. 5. Mettre à jour `LifePlay.Build.cs`, `LifePlayEditorTools.Build.cs`, `LifePlay.uproject`, les deux `*.Target.cs`. 6. Regénérer le projet (`GenerateProjectFiles`) et compiler Editor + Game ; lancer la validation (commandlets `LPAnimEval`, `LPAnimCatalog`, `LPAnimTwist`) pour contrôler l'absence de régression.