# 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` : 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` : 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 : `"/%s.%s"` avec `Prop.Name` issu du `.lpworld` | `LPPairSandbox.cpp:857` | | `FurnitureWallMaterial` | `Sandbox.Assets` | `/Game/Heavy/New2/Furniture/Materials/FntWall.FntWall` | `LoadObject` : matériau des murs/coque de la chambre | `LPPairSandbox.cpp:877` | | `DazAnimRoot` * | `Sandbox.Roots` | `/Game/Heavy/Daz/Anims` | Racine des clips Daz : paires `"/%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` (`"/%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` 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.