LifePlay-Revival/Docs/Migration/etat/sandbox/S1/S1-SPEC.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

107 lines
9.4 KiB
Markdown

# S1-T1 — Contrat `Config/LPSandbox.ini`
Rôle : A-ARCH. Périmètre : **spécification uniquement** (aucun changement runtime dans cette fiche).
Source analysée : `Source/LifePlay/LPPairSandbox.cpp` (et `.h`, qui ne contient aucun littéral de chemin : seulement `QuaterniusMesh` / `QuaterniusClips` en `FString`, lignes 261-262) + `Source/LifePlay/LPAnimRedirect.cpp:22` (helper `ClipPath` utilisé par le sandbox).
## 1. Décisions de contrat
| Décision | Choix |
|---|---|
| Emplacement | `Config/LPSandbox.ini` (projet, versionné). **Pas** `Saved/`. |
| Lecture (S2, hors périmètre ici) | `FConfigFile::Read(FPaths::Combine(FPaths::ProjectConfigDir(), TEXT("LPSandbox.ini")))` — lecture directe, non hiérarchique (pas de `DefaultLPSandbox.ini`). |
| Fichier absent / clé absente | Repli sur le **default = littéral actuel du code** (colonne « default »). Le comportement actuel doit rester identique sans ini. |
| Section | Une section par thème : `[Sandbox.Mode]`, `[Sandbox.Assets]`, `[Sandbox.Roots]`, `[Sandbox.Content]`, `[Sandbox.Sky]`, `[Sandbox.Output]`. |
| Convention de chemin asset | Chemin UE complet `/Game/...Nom.Nom` (format `LoadObject`), sauf mention « racine » (sans `.Nom`). |
| Convention de chemin disque | Relatif à `FPaths::ProjectContentDir()` / `ProjectSavedDir()` selon la clé (le préfixe est noté dans l'usage). |
| Hors contrat (volontairement) | `Saved/Config/Windows/LPKeyboard.ini` (config clavier utilisateur, reste en Saved), `Saved/sandbox_mode.txt` et `Saved/sandbox_tour.txt` (déclencheurs éphémères consommés au lancement), littéraux `/Engine/...` (Cube, `BP_Sky_Sphere`), noms de clips/poses (`Idle_Loop`, `Walk_Loop`, `A_TPose`, préfixes `B_`/`M_`), liste des 43 poses `Names[]` (l. ~3365-3376). |
## 2. Tableau des clés
Les clés notées `*` sont critiques (demandées par la fiche). Numéros de ligne = `LPPairSandbox.cpp` au moment de l'analyse.
| clé ini | section | valeur default | usage dans code | fichier:ligne |
|---|---|---|---|---|
| `PersonBP` * | `Sandbox.Assets` | `/Game/BPs/Person.Person_C` | `LoadObject<UClass>` : lecture des défauts de capsule (rayon, demi-hauteur) dans `ALPPairSandboxStage::Build` | `LPPairSandbox.cpp:844` |
| `PersonBP` (même clé, 2e usage) | `Sandbox.Assets` | idem | `LoadObject<UClass>` : copie du profil/réponses de collision de la capsule dans `ConfigureCapsule` | `LPPairSandbox.cpp:1089` |
| `WorldFile` * | `Sandbox.Content` | `Modules/vin_Base/World/work___vin01.lpworld` | Fichier de la chambre (props + échelle) ; chemin = `ProjectContentDir()` + valeur ; passé à `PairSandbox::LoadWorldFile` | `LPPairSandbox.cpp:832` |
| `ModuleRoot` * | `Sandbox.Content` | `Modules/vin_Base` | Racine du scan récursif `*.lpaction` (catalogue d'actions Interaction) ; `ProjectContentDir()` + valeur | `LPPairSandbox.cpp:622` |
| `ChatAction` | `Sandbox.Content` | `Modules/vin_Base/Interactions/chat.lpaction` | Lecture de `ACTION_NAME` pour `ChatLabel` ; `ProjectContentDir()` + valeur | `LPPairSandbox.cpp:1045` |
| `FurnitureRoot` * | `Sandbox.Roots` | `/Game/Heavy/New2/Furniture` | Racine des meshes de meubles : `"<racine>/%s.%s"` avec `Prop.Name` issu du `.lpworld` | `LPPairSandbox.cpp:857` |
| `FurnitureWallMaterial` | `Sandbox.Assets` | `/Game/Heavy/New2/Furniture/Materials/FntWall.FntWall` | `LoadObject<UMaterialInterface>` : matériau des murs/coque de la chambre | `LPPairSandbox.cpp:877` |
| `DazAnimRoot` * | `Sandbox.Roots` | `/Game/Heavy/Daz/Anims` | Racine des clips Daz : paires `"<racine>/%s%s.%s%s"` (Pair + `_F`/`_M`) ; idles Daz | `LPPairSandbox.cpp:2610`, `3106`, `1078-1079` |
| `DazIdleFemale` | `Sandbox.Assets` | `/Game/Heavy/Daz/Anims/Idle_F_Daz.Idle_F_Daz` | `PlayIdle(bFemale = true)` | `LPPairSandbox.cpp:1078` |
| `DazIdleMale` | `Sandbox.Assets` | `/Game/Heavy/Daz/Anims/idle_Daz.idle_Daz` | `PlayIdle(bFemale = false)` | `LPPairSandbox.cpp:1079` |
| `BridgetMesh` * | `Sandbox.Assets` | `/Game/Heavy/Daz/Bridget/bridget_gp.bridget_gp` | Corps Bridget : `ApplyBody("Bridget")` et mode Mesure (`Place("Bridget", …)`) | `LPPairSandbox.cpp:3026`, `1529` |
| `MichaelMesh` * | `Sandbox.Assets` | `/Game/Heavy/Daz/Michael/michael.michael` | Corps Michael : `ApplyBody("Michael")` | `LPPairSandbox.cpp:3030` |
| `QuaterniusRoot` * | `Sandbox.Roots` | `/Game/Quaternius` | `ScanPathsSynchronous` de l'Asset Registry avant détection du corps/clips | `LPPairSandbox.cpp:2962` |
| `QuaterniusUALRoot` * | `Sandbox.Roots` | `/Game/Quaternius/UAL` | `GetAssetsByPath` (détection auto du mesh le plus riche en bones + clips non `B_`/`M_`) ; sert aussi de base à `LPAnimRedirect::ClipPath` (`"<racine>/%s.%s"`) | `LPPairSandbox.cpp:2964`, `LPAnimRedirect.cpp:22` |
| `QuaterniusUALDisk` | `Sandbox.Content` | `Quaternius/UAL` | Dossier disque scanné (`*.uasset`) pour lister les clips ayant une copie `B_` (43 clips) ; `ProjectContentDir()` + valeur | `LPPairSandbox.cpp:1894` |
| `QuaterniusMannequinMesh` | `Sandbox.Assets` | `/Game/Quaternius/UAL/UAL1_Standard.UAL1_Standard` | Mannequin de référence en mode Mesure (`Place("Mannequin", …)`) | `LPPairSandbox.cpp:1528` |
| `Mode` * | `Sandbox.Mode` | `Pair` | Mode initial du menu. Valeurs : `Pair`, `Interaction`, `Mesure` (liste `Modes`, `Modes[0]` = `Pair`). Aujourd'hui seul `Mesure` est activable, via `Saved/sandbox_mode.txt` ; l'ini fournirait le mode persistant. Le `.txt` éphémère garde la priorité (compat) | `LPPairSandbox.cpp:3396-3399`, `3907-3921` |
| `SkyCubemap` * (opt.) | `Sandbox.Sky` | `/Game/Heavy/New2/Buildings/Mats/urban_street_03_1k.urban_street_03_1k` | `LoadObject<UTextureCube>` pour `SkyLight->SetCubemap` (SkyLight `SLS_SpecifiedCubemap`) | `LPPairSandbox.cpp:1001` |
| `SkyIntensity` (opt.) | `Sandbox.Sky` | `0.6` | `SkyLight->SetIntensity(0.6f)` | `LPPairSandbox.cpp:1000` |
| `MeasureOutputRoot` | `Sandbox.Output` | `Measurement` | Sous-dossier de `ProjectSavedDir()` pour `shots`, `tour`, `vignettes`, `planches`, `retarget_pose_*.txt` (les sous-noms restent en dur) | `LPPairSandbox.cpp:1687`, `1862`, `1873`, `1885`, `1930-1931` |
### Notes
- `PersonBP` n'a qu'une clé pour deux sites d'appel (844 et 1089) : en S2 un seul accesseur partagé.
- `DazAnimRoot` : les deux idles (`DazIdleFemale/Male`) sont des clés distinctes car leurs noms ne suivent pas le motif `%s%s.%s%s` (casse différente `Idle_F_Daz` / `idle_Daz`) ; les valeurs par défaut sont donc les littéraux exacts, y compris la casse.
- `Walk_Loop`, `Idle_Loop`, `A_TPose`, préfixes `B_`/`M_` : conventions de nommage Quaternius/retarget, non exposées (hors contrat) ; seul leur dossier (`QuaterniusUALRoot`) l'est.
- Les préfixes disque vs chemin UE (`Quaternius/UAL` vs `/Game/Quaternius/UAL`) sont deux clés séparées pour ne pas dériver un chemin de l'autre (l'actuel code les écrit indépendamment).
- Packaging : un `Config/LPSandbox.ini` custom n'est staged que s'il est listé (UFS/`DirectoriesToAlwaysStageAsUFS` ou `+DirectoriesToAlwaysStageAsNonUFS`) ; le sandbox est un outil éditeur/dev, donc à traiter en S2 uniquement si le sandbox doit tourner packagé.
## 3. Contenu proposé de `Config/LPSandbox.ini`
Les valeurs ci-dessous sont **identiques** aux littéraux actuels : charger ce fichier ne doit rien changer au comportement.
```ini
; Config/LPSandbox.ini
; Contrat S1-T1 — chemins et options du bac a sable LPPairSandbox.
; Toute cle absente => valeur par defaut du code (identique a celle-ci).
; Chemins "/Game/..." : format LoadObject (/Package/Nom.Nom ou /Package/BP.BP_C).
; Chemins relatifs (sans "/") : relatifs a Content/ sauf [Sandbox.Output] (relatif a Saved/).
[Sandbox.Mode]
; Mode initial du menu : Pair | Interaction | Mesure
; (Saved/sandbox_mode.txt, s'il existe, reste prioritaire et est consomme.)
Mode=Pair
[Sandbox.Assets]
PersonBP=/Game/BPs/Person.Person_C
BridgetMesh=/Game/Heavy/Daz/Bridget/bridget_gp.bridget_gp
MichaelMesh=/Game/Heavy/Daz/Michael/michael.michael
QuaterniusMannequinMesh=/Game/Quaternius/UAL/UAL1_Standard.UAL1_Standard
DazIdleFemale=/Game/Heavy/Daz/Anims/Idle_F_Daz.Idle_F_Daz
DazIdleMale=/Game/Heavy/Daz/Anims/idle_Daz.idle_Daz
FurnitureWallMaterial=/Game/Heavy/New2/Furniture/Materials/FntWall.FntWall
[Sandbox.Roots]
; Racines de packages UE (sans nom d'asset final)
FurnitureRoot=/Game/Heavy/New2/Furniture
DazAnimRoot=/Game/Heavy/Daz/Anims
QuaterniusRoot=/Game/Quaternius
QuaterniusUALRoot=/Game/Quaternius/UAL
[Sandbox.Content]
; Chemins disque, relatifs a Content/
ModuleRoot=Modules/vin_Base
WorldFile=Modules/vin_Base/World/work___vin01.lpworld
ChatAction=Modules/vin_Base/Interactions/chat.lpaction
QuaterniusUALDisk=Quaternius/UAL
[Sandbox.Sky]
; Optionnel : cubemap du SkyLight du sandbox
SkyCubemap=/Game/Heavy/New2/Buildings/Mats/urban_street_03_1k.urban_street_03_1k
SkyIntensity=0.6
[Sandbox.Output]
; Relatif a Saved/
MeasureOutputRoot=Measurement
```
## 4. Critères d'acceptation pour S2 (implémentation, hors de cette fiche)
1. Sans `Config/LPSandbox.ini`, comportement strictement identique à aujourd'hui (defaults = littéraux de la colonne « default »).
2. Avec le fichier d'exemple ci-dessus, comportement strictement identique.
3. Un seul point de lecture (struct de config chargée une fois, p. ex. `FLPSandboxConfig::Get()`), plus de `TEXT("/Game/...")` en dur dans `LPPairSandbox.cpp`/`LPAnimRedirect.cpp` pour les clés ci-dessus.
4. Clé présente mais asset introuvable : même journal d'erreur qu'aujourd'hui (`LogLifePlay`), sans repli silencieux sur une autre valeur.