SB1 : registre des mods de la sandbox, barre laterale, modes enveloppes en mods (specification, implementation, build, validation ; pin 82dae58)

Phase SB1 (CHG-229).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
edujiro5972 2026-10-07 15:50:03 +02:00
parent 4169638b44
commit 316592551b
7 changed files with 461 additions and 4 deletions

View file

@ -117,12 +117,12 @@ D:\Game\
- **Liaison** : `LifePlay.uproject` déclare `"AdditionalPluginDirectories": ["../LifePlay-Sandbox/Plugins"]` (chemin relatif au `.uproject`) et active `LPSandbox` dans `Plugins`. UBT et l'éditeur y trouvent `LPSandbox.uplugin` (modules `LPSandbox` et `LPAnimMeasure`) ; les DLL du plugin sont compilées avec le jeu (`Build.bat LifePlayEditor Win64 Development`).
- **Pourquoi pas un submodule** : l'addon est un `uproject` mince qui contient le plugin sous `Plugins/LPSandbox/`. Un submodule monté sur `Plugins/LPSandbox` du jeu donnerait `Plugins/LPSandbox/Plugins/LPSandbox/` (plugin imbriqué, non scanné au bon niveau) ; un submodule `External/LifePlay-Sandbox` dupliquerait l'addon à côté du sibling et rendrait ambigu l'exemplaire chargé. Un clone sibling évite les deux problèmes.
- **Pin** : le jeu consomme un commit précis de l'addon, consigné à la main dans `S3-INT.md` (aujourd'hui `3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362`, `lot/SB0-conception`, SB0 conception de la sandbox modulaire ; précédents : `f09c42e5a2bc613cf269cce76c878461ca41c8b7` S7-T2 uplugin, `c1fdd29e2c1036c7d6365ba6389eff5d2f8c8c96` S5-T2 `LPCoupleSim`, `f8102647ccdebad37e4977ccbf2c51e583823a80`, S4-T3). Le pin est mis à jour (hash dans `S3-INT.md` et README de l'addon) à chaque évolution de l'addon que le jeu doit prendre. Reproduire :
- **Pin** : le jeu consomme un commit précis de l'addon, consigné à la main dans `S3-INT.md` (aujourd'hui `82dae58380b76c45f60541fd7cb5a0a409700e10`, `lot/SB1-registre`, SB1 registre de mods et barre latérale ; précédents : `3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362` SB0 conception de la sandbox modulaire, `f09c42e5a2bc613cf269cce76c878461ca41c8b7` S7-T2 uplugin, `c1fdd29e2c1036c7d6365ba6389eff5d2f8c8c96` S5-T2 `LPCoupleSim`, `f8102647ccdebad37e4977ccbf2c51e583823a80`, S4-T3). Le pin est mis à jour (hash dans `S3-INT.md` et README de l'addon) à chaque évolution de l'addon que le jeu doit prendre. Reproduire :
```powershell
cd D:\Game
git clone https://github.com/edujiro5972/LifePlay-Sandbox.git
git -C LifePlay-Sandbox checkout 3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362
git -C LifePlay-Sandbox checkout 82dae58380b76c45f60541fd7cb5a0a409700e10
# le jeu doit être cloné à côté : D:\Game\LifePlay-master 5.5
```
@ -170,6 +170,9 @@ Le lot S5 ferme la **dérive de fork** entre la sandbox (`LPSandbox/LPCoupleSim.
- [ ] Parcours Show complet en jeu et décision d'alignement du plancher (-90 / -88).
- [x] Commit addon `f09c42e` poussé vers `origin/main` (constaté en S8 : `origin/main` = `dae6206`, qui le contient).
- [ ] Migration commitée **côté jeu** (`LifePlay.uproject`, `Config/LPSandbox.ini`, `Build.cs`, `LPShowCoupleSubsystem.*`, docs `21` / `22`, `etat/sandbox/`) : **non fait** (`22` § 6).
- [x] **SB0** : conception de la sandbox modulaire (addon, pin `3b21fd1`, tag `lot-SB0-valide`).
- [x] **SB1** : registre de mods, barre latérale, 7 descripteurs `.lpmod`, modes Pair / Interaction / Mesure enveloppés en `lps_couple` / `lps_interaction` / `lps_asset_integration` (`etat/sandbox/SB1/SB1-VAL.md` PASS, contrôle visuel utilisateur 2026-10-07 ; tag `lot-SB1-valide`).
- [ ] Reports SB1 (registre unique, lot propriétaire **SB2**) : chaque ouverture de mod réécrit `Open` dans `Saved/sandbox_mods.ini` (une tournée automatique laisse donc `Open=lps_asset_integration`) ; images de la tournée Mesure : empreintes différentes d'une exécution à l'autre (JSON et CSV identiques), la non-régression stricte de SB2-T5 doit comparer les JSON/CSV, et les images visuellement ou par écart de pixels.
## Lots (S0 … S8)
| Lot | Objet (une ligne) |

View file

@ -1131,6 +1131,7 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
| CHG-155 | 2026-10-05 | ShowCouple | Un lit est reconnu par son nom. Il ne bloque un déplacement que vers le bas, pour se poser ; le retour horizontal n'est plus arrêté et le contact ne renvoie plus la boîte | après avoir quitté le lit, la boîte ne pouvait plus y revenir | revenir au test `DoesPackageExist` et au filtre du flanc seul |
| CHG-156 | 2026-10-05 | Clavier | `LPKeyboardSubsystem` : bouton « Clavier » ajouté à l'exécution dans `MainMenu.SettingsScrollBox` ; panneau pour changer, ajouter ou retirer une touche (actions et axes clavier). Écrit dans `Saved/Config/Windows`, pas dans `GameSettings.sav` | pouvoir réaffecter les touches, dont Q, sans modifier les Blueprints | retirer `LPKeyboardSubsystem.h/.cpp` et la dépendance `UMG` |
| CHG-158 | 2026-10-05 | Réglages | `LPSettingsSubsystem` : onglets Audio, Video, Lewd, General, Keyboard, Misc au-dessus de `SettingsScrollBox`. Les lignes du Blueprint restent en place et sont montrées selon leur texte ; le reste va dans Misc. Keyboard affiche la liste des touches | la liste unique ne montrait pas le clavier | retirer `LPSettingsSubsystem.h/.cpp` |
| CHG-229 | 2026-10-07 | SB1 | addon `D:\Game\LifePlay-Sandbox` : `Plugins/LPSandbox/Source/LPSandbox/*` (registre `FLPSandboxModRegistry`, journal `LogLPSandboxMods`, `Saved/sandbox_mods.ini`, barre latérale, enveloppes), `Plugins/LPSandbox/Modules/` (7 descripteurs `.lpmod`), `README.md` (commits `82dae58380b76c45f60541fd7cb5a0a409700e10` code + commit pin, tag `lot-SB1-valide`, branche `lot/SB1-registre`) ; jeu : `Docs/Migration/etat/sandbox/SB1/SB1-VAL.md`, `Docs/Migration/etat/sandbox/S3/S3-INT.md`, `Docs/Migration/21_Sandbox_Addon.md` (pin, SB1, reports) | lot SB1 : registre de mods, barre latérale, 7 descripteurs ; modes Pair / Interaction / Mesure enveloppés en `lps_couple` / `lps_interaction` / `lps_asset_integration` ; validation E1 à E5 PASS + contrôle visuel utilisateur (a, b, c, barre latérale, images E5 : pas de défauts constatés) | pin `3b21fd1` → `82dae58` (hash complet `82dae58380b76c45f60541fd7cb5a0a409700e10`) | `.\Tools\Restore.ps1 -Lot SB1` (docs du jeu), `.\Tools\Restore.ps1 -Lot SB1-addon -ProjectRoot D:\Game\LifePlay-Sandbox` et `git -C D:\Game\LifePlay-Sandbox checkout lot-SB1-avant` ; rien n'est commité dans le dépôt du jeu ni poussé |
| CHG-228 | 2026-10-07 | SB0 | addon `D:\Game\LifePlay-Sandbox` : `Docs/SB0-conception.md`, `Docs/SB-plan.md`, `README.md` (commits C1 `3b21fd1`, C2 `12d6198`, tags `lot-SB0-avant` / `lot-SB0-valide`, branche `lot/SB0-conception`) ; jeu : `Docs/Migration/etat/sandbox/S3/S3-INT.md`, `Docs/Migration/21_Sandbox_Addon.md` (pin) | conception de la sandbox modulaire demandée par l'utilisateur (modes Pair/Interaction/Mesure → mods `lps_couple` / `lps_interaction` / `lps_asset_integration`, modules système et bibliothèques `lps_*`, `.lpmod` étendu rétrocompatible, interface `ILPSandboxMod`, registre, barre latérale ; plan SB0–SB4 au format du cahier S0–S8) | pin `a3b1b89` → `3b21fd1` (hash complet `3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362`) | `.\Tools\Restore.ps1 -Lot SB0` (jeu) et `.\Tools\Restore.ps1 -Lot SB0-addon -ProjectRoot D:\Game\LifePlay-Sandbox` ; commits addon conservés dans l'historique (branche) ; rien n'est commité dans le dépôt du jeu ni poussé |
| CHG-227 | 2026-10-07 | DOC-MOD-3 | `Docs/Migration/23_Cycle_De_Vie_Mod.md` (§ 2.6 nouveau, 7 schémas Mermaid) | schémas du cycle de vie d'un module demandés par l'utilisateur : états (installé, monté, listé, inscrit, actif / inactif, retiré, déchargé), séquence de démarrage (moteur, registre, `GI`, sous-systèmes C++), arbres logiques de `GI.LoadSettings(LoadLastSave)`, `GI.PopulateRaws()`, du gestionnaire (`CreateModManagerWidget(GameStart)`, `ToggleMod(Mod, IsEnabled)`, `SortLoadOrder(ForUp, ModuleIndex)`, `CloseModManager(GameStart)`), appel d'une animation de mod, désactivation et déchargement ; graphes reconstitués depuis le relevé `LPDumpFunction -BP=GI -Fn=EventGraph -Pins -Exec` et l'ordre d'initialisation de `UGameInstance` (moteur) ; 7 schémas rendus sans erreur par Mermaid 11 | aucun changement de code ni de configuration | `.\Tools\Restore.ps1 -Lot DOC-MOD-3` |
| CHG-226 | 2026-10-07 | DOC-MOD-2 | `Docs/Migration/23_Cycle_De_Vie_Mod.md` (§ 2.4, 3.1, 3.2, 3.3 et 3.4 nouveaux, 5, 6) ; `Docs/Migration/Journal_Modifications.md` | complément de la documentation du cycle de vie d'un mod, constats vérifiés dans les Blueprints (`GI`, `UI/ModuleTick`, relevés `LPDumpFunction`) et le moteur UE 5.5 : l'ordre de chargement existe (`GI.SortLoadOrder`, `PopulateRaws` : le premier fichier lu par nom gagne, le module le plus bas l'emporte, fichiers texte seulement) ; `vin_Base` verrouillé en dur dans `UI/ModuleTick` ; dépendance `.uplugin` obligatoire absente = le jeu ne démarre pas, donc aucune dépendance entre mods dans le `.uplugin` ; doublons entre `.uplugin`, `.lpmod` et `pack.json`, divergences dues à `New-IfMissing` de `DeliverAnims.ps1` ; évolutions E12 corrigée, E13 à E15 ajoutées | aucun changement de code ni de configuration | `.\Tools\Restore.ps1 -Lot DOC-MOD-2` |

View file

@ -8,7 +8,7 @@ Date : 2026-10-06 — Role A-INT
|---|---|
| Repo addon | https://github.com/edujiro5972/LifePlay-Sandbox.git |
| Branche | `main` |
| **Commit pin** | `3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362` (`3b21fd1`, SB0, **local, non pousse** au 2026-10-07) |
| **Commit pin** | `82dae58380b76c45f60541fd7cb5a0a409700e10` (`82dae58`, SB1, **local, non pousse** au 2026-10-07) |
| Contenu du commit | S5-T2 : `LPCoupleSim` (géométrie du couple partagée jeu/addon) ; inclut S4-T3 (retrait GM Interaction legacy), S4-T2 (découpe `LPPairSandbox.cpp`) et S3-T4 (`StartupModule` → `SetUALRoot`) |
| Pins précédents | `f09c42e5a2bc613cf269cce76c878461ca41c8b7` (S7-T2), `c1fdd29e2c1036c7d6365ba6389eff5d2f8c8c96` (S5-T2), `f8102647ccdebad37e4977ccbf2c51e583823a80` (S4-T3), `4de1f0b6764ed3bc575bb687630d3f070e1be221` (S3-T4) |
@ -46,7 +46,7 @@ sur la copie effectivement chargee. Pin manuel a la place (ci-dessous).
```powershell
cd D:\Game
git clone https://github.com/edujiro5972/LifePlay-Sandbox.git
git -C LifePlay-Sandbox checkout 3b21fd180c04f0fa49a1b5c9ef1b517ade1cb362
git -C LifePlay-Sandbox checkout 82dae58380b76c45f60541fd7cb5a0a409700e10
# Le jeu doit etre clone a cote : D:\Game\LifePlay-master 5.5
```

View file

@ -0,0 +1,70 @@
# Fiche SB1-T4 — A-BUILD
**Modèle prévu:** haiku (M0)
**Modèle utilisé:** haiku
**Agent:** A-BUILD
---
## Commande de compilation
```powershell
Set-Location 'D:\Game\LifePlay-master 5.5'
& 'D:\Game\Launcher\Engine\UE_5.5\Engine\Build\BatchFiles\Build.bat' LifePlayEditor Win64 Development -Project='D:\Game\LifePlay-master 5.5\LifePlay.uproject' -WaitMutex *> 'D:\Game\LifePlay-master 5.5\Saved\Logs\portage\SB1_build.log'
```
---
## Résultats
- **Code de sortie:** 0 (succès)
- **Durée totale:** 42.95 secondes
- **Nombre d'actions compilées:** 4
- [1/4] Compile [x64] Module.LPSandbox.cpp
- [2/4] Link [x64] UnrealEditor-LPSandbox.lib
- [3/4] Link [x64] UnrealEditor-LPSandbox.dll
- [4/4] WriteMetadata LifePlayEditor.target
---
## Erreurs et avertissements
**Erreurs:** Aucune
**Avertissements C++:** Aucun
---
## Vérification des DLL
### Binaires du jeu LifePlay
```
UnrealEditor-LPSandbox.dll
Nom: UnrealEditor-LPSandbox.dll
Date: 07/10/2026 11:33:43
Chemin: D:\Game\LifePlay-master 5.5\Binaries\Win64\UnrealEditor-LPSandbox.dll
```
### Plugin LPSandbox externe
Dossier `D:\Game\LifePlay-Sandbox\Plugins\LPSandbox\Binaries` : **n'existe pas**
---
## Journal de compilation
**Chemin:** `D:\Game\LifePlay-master 5.5\Saved\Logs\portage\SB1_build.log`
### Extrait clé
```
Total time in Unreal Build Accelerator local executor: 21.57 seconds
Total execution time: 42.95 seconds
```
---
## Conclusion
Compilation **réussie**. Le plugin LPSandbox a été compilé et linkée avec succès. La DLL `UnrealEditor-LPSandbox.dll` est présente dans `D:\Game\LifePlay-master 5.5\Binaries\Win64\` avec un horodatage récent (11:33:43).

View file

@ -0,0 +1,26 @@
# SB1-IMPL — Registre, interface, enveloppes (SB1-T2)
- **Fiche** : SB1-T2 — **Agent** : A-IMPL-H — **Modèle** : prévu opus (M3) | utilisé opus (session principale)
- **Dépôt** : addon, branche `lot/SB1-registre` (tag d'entrée `lot-SB1-avant` = `12d6198`)
- **Instantané** : `LifePlay_Backup_5.5\SB1-addon` (`LPSandbox.cpp`, `LPSandbox.Build.cs`, `LPPairSandbox.h/.cpp`, `LPPairSandboxMenu.h`)
## Fichiers
| Fichier | Nature |
|---|---|
| `LPSandboxMod.h` | nouveau : types (§ 2 de la SPEC) |
| `LPSandboxModRegistry.h/.cpp` | nouveau : registre (§ 3-4), journal `LogLPSandboxMods` |
| `LPSandboxModWrappers.h/.cpp` | nouveau : enveloppes `lps_couple` (Pair), `lps_interaction` (Interaction), `lps_asset_integration` (Mesure) |
| `LPSandboxSidebar.h` | nouveau : déclaration du widget (implémentation : SB1-T3) |
| `LPSandbox.cpp` | `StartupModule` : `RegisterAll()` puis `Scan()` |
| `LPSandbox.Build.cs` | `Projects` en dépendance privée |
| `LPPairSandbox.h/.cpp` | `OpenModeLabel`, `OpenNeutral`, `OpenInitialMod`, `MakeModContext` ; barre latérale à la place de la liste des modes ; reste du panneau dans `BodyRow` (repliable) ; `EndPlay` → `Registry.EndSession()` |
| `LPPairSandboxMenu.h` | `BodyRow` |
## Écarts à la SPEC (décisions de l'implémenteur)
1. `EndSession()` ajouté au registre : à la fin de partie, le mod ouvert est oublié sans appel (la scène est détruite) ; évite un pointeur de personnage pendant entre deux sessions.
2. Scène neutre : `CloseOpen` du registre appelle `Player->OpenNeutral()` (Pair sans liste de clips) ; `OpenModeLabel` remet la liste de clips visible au retour sur Pair (`SelectMode` ne fait rien si le mode est déjà Pair).
3. `Saved/sandbox_mode.txt` : le fichier est remis à `Pair` après lecture (comportement existant) ; `Pair` est désormais traité comme « pas de déclencheur », sinon il masquerait à chaque lancement le mod ouvert mémorisé. Avant SB1, un fichier `Pair` masquait aussi `[Sandbox.Mode]` ; après, `[Sandbox.Mode]` n'est consulté que s'il n'y a ni déclencheur ni mod mémorisé.
4. Doublons : contrôlés par source (sandbox / jeu) ; un module du jeu peut porter le même identifiant qu'un module de la sandbox sans refus.
5. Le registre cherche un prérequis dans la sandbox puis dans le jeu (un module du jeu présent satisfait un prérequis).

View file

@ -0,0 +1,244 @@
# SB1-SPEC — Registre de mods et barre latérale (modes enveloppés)
- **Fiche** : SB1-T1 — **Agent** : A-ARCH — **Modèle** : prévu opus (M4) | utilisé opus
- **Dépend de** : SB0 (`D:\Game\LifePlay-Sandbox\Docs\SB0-conception.md`, commit `3b21fd1`, tag `lot-SB0-valide`)
- **Dépôt** : addon `D:\Game\LifePlay-Sandbox`, branche `lot/SB1-registre` créée depuis `lot-SB0-valide`, tag `lot-SB1-avant`
- **Principe SB1** : aucun code de mode n'est déplacé. Les trois modes actuels deviennent trois mods **logiques**
(descripteur + enveloppe qui appelle `SelectMode` existant). La liste déroulante des modes est remplacée par la
barre latérale. Comportement de chaque mode strictement inchangé.
## 1. Fichiers
| Fichier (sous `Plugins/LPSandbox/Source/LPSandbox/`) | Fiche | Contenu |
|---|---|---|
| `LPSandboxMod.h` (nouveau) | T2 | descripteur, états, contexte, interface `ILPSandboxMod` |
| `LPSandboxModRegistry.h/.cpp` (nouveaux) | T2 | registre (§ 3), persistance (§ 4), journal |
| `LPSandboxModWrappers.cpp` (nouveau) | T2 | 3 enveloppes + enregistrement (§ 5) |
| `LPSandbox.cpp` | T2 | `StartupModule` : enregistrement des fabriques, `Scan()` |
| `LPPairSandbox.h/.cpp` | T2 | `OpenModeLabel(const FString&)` public ; choix du mod initial (§ 6) ; emplacement de la barre (§ 7) |
| `LPSandbox.Build.cs` | T2 | `"Projects"` en dépendance privée (`IPluginManager`) |
| `LPSandboxSidebar.h/.cpp` (nouveaux) | T3 | widget Slate `SLPSandboxSidebar` (§ 7) |
| `Plugins/LPSandbox/Modules/**/<id>.lpmod` (nouveaux) | T3 | 7 descripteurs (§ 8) |
## 2. Types (`LPSandboxMod.h`)
```cpp
#pragma once
#include "CoreMinimal.h"
class SWidget;
class ALPPairSandboxCharacter;
class ALPPairSandboxStage;
enum class ELPSandboxModKind : uint8 { System, Library, Mod, Data }; // Data = .lpmod du jeu, lecture seule
enum class ELPSandboxModSource : uint8 { Sandbox, Game };
enum class ELPSandboxModState : uint8
{
Ready, // utilisable (mod activé, prérequis satisfaits)
Disabled, // case décochée
Missing, // prérequis absent ou désactivé (Reason renseignée)
Refused, // règle violée : doublon, mod requis par un mod, cycle (Reason renseignée)
NoFactory, // descripteur de la sandbox sans code enregistré
DataOnly // descripteur du jeu
};
struct FLPSandboxModDescriptor
{
FString Id; // MODULE_UNIQUEID
FString Name; // MODULE_NAME
FString Author; // MODULE_AUTHOR
FString Description, Version, License, Link, AuthorLink;
TArray<FString> Requirements; // MODULE_REQUIREMENTS, séparés par ',' ; espaces retirés ; vides ignorés
ELPSandboxModKind Kind = ELPSandboxModKind::Mod;
ELPSandboxModSource Source = ELPSandboxModSource::Sandbox;
bool bSystem = false, bHidden = false, bLocked = false, bDefaultEnabled = false;
int32 Index = -1; // index hexadécimal du dossier (0001_…), -1 sans index
FString Folder; // chemin complet du dossier
FString File; // chemin complet du .lpmod
TArray<FString> Warnings; // lecture : clé manquante, UNIQUEID ≠ nom du fichier, 3 premières lignes dans le désordre…
TMap<FString, FString> Keys; // toutes les clés lues (majuscules)
};
struct FLPSandboxContext
{
ALPPairSandboxCharacter* Player = nullptr; // SB1 : les enveloppes passent par le personnage joueur
ALPPairSandboxStage* Stage = nullptr;
};
class ILPSandboxMod
{
public:
virtual ~ILPSandboxMod() = default;
virtual FString GetId() const = 0;
virtual void Activate(const FLPSandboxContext& Context) = 0; // le mod prend la scène
virtual void Deactivate(const FLPSandboxContext& Context) = 0; // il la rend (SB1 : rien, SelectMode gère la transition)
virtual TSharedPtr<SWidget> BuildPanel() { return nullptr; } // SB1 : nul (les rangées actuelles du menu restent)
virtual void Tick(float DeltaSeconds) {}
};
```
## 3. Registre (`FLPSandboxModRegistry`, singleton `Get()`)
### 3.1 API (figée : la barre latérale de T3 n'utilise que celle-ci)
```cpp
struct FLPSandboxModEntry
{
FLPSandboxModDescriptor Desc;
ELPSandboxModState State = ELPSandboxModState::Disabled;
FString Reason; // texte affichable (infobulle ⚠)
bool bEnabled = false;
bool bOpen = false;
};
class LPSANDBOX_API FLPSandboxModRegistry
{
public:
static FLPSandboxModRegistry& Get();
void RegisterFactory(const FString& Id, TFunction<TSharedRef<ILPSandboxMod>()> Factory);
void Scan(); // découverte + lecture + contrôles + état persisté (§ 3.2-3.4)
const TArray<FLPSandboxModEntry>& GetEntries() const; // ordre d'affichage : système/bibliothèques, mods, jeu
const FLPSandboxModEntry* Find(const FString& Id) const;
bool SetEnabled(const FString& Id, bool bEnabled); // refus si verrouillé ou Data ; recalcul des états ; Save()
bool Move(const FString& Id, int32 Delta); // mods seulement (-1 monte, +1 descend) ; Save()
bool Open(const FString& Id, const FLPSandboxContext& Context); // Ready + activé exigé ; Deactivate du mod ouvert, Activate du nouveau ; Save()
FString GetOpenId() const;
FString IdForModeLabel(const FString& Label) const; // "Pair"→lps_couple, "Interaction"→lps_interaction, "Mesure"→lps_asset_integration
FSimpleMulticastDelegate OnChanged; // diffusé après tout changement
};
```
### 3.2 Découverte
- Sandbox : `IPluginManager::Get().FindPlugin(TEXT("LPSandbox"))->GetBaseDir()` + `/Modules/System/*/` et
`/Modules/Mods/*/` ; dans chaque dossier, le premier `*.lpmod`.
- Jeu (lecture seule) : `FPaths::ProjectContentDir() / TEXT("Modules")` ; `<dossier>/<dossier>.lpmod` (même règle
que `ULPModInfoSubsystem` du jeu). Source = Game, Kind = Data, état DataOnly. Absent (projet `LPSandbox.uproject`
seul) : section Jeu vide, sans erreur.
- Index : si le nom du dossier commence par 4 chiffres hexadécimaux suivis de `_`, `Index` = valeur ; sinon -1.
### 3.3 Lecture d'un `.lpmod`
- Ligne `CLÉ: valeur` : clé = texte avant le premier `:`, sans espaces, mise en majuscules ; valeur coupée aux
extrémités ; lignes vides et commençant par `//` ignorées ; première occurrence retenue.
- Clés lues : `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_VERSION`,
`MODULE_LICENSE`, `MODULE_LINK`, `MODULE_AUTHOR_LINK`, `MODULE_REQUIREMENTS`, `MODULE_SYSTEM`, `MODULE_HIDDEN`,
`MODULE_LOCKED`, `MODULE_KIND`, `MODULE_DEFAULT_ENABLED` (booléens : `true` insensible à la casse, sinon faux).
- Défauts : `Kind` = `system` si `MODULE_SYSTEM: true`, `library` si l'identifiant commence par `lps_lib_`, sinon `mod` ;
`MODULE_KIND` explicite prime. `bHidden` et `bLocked` valent `bSystem || Kind==Library` si la clé est absente.
`bDefaultEnabled` vaut vrai pour système/bibliothèque, faux pour un mod si la clé est absente.
- Avertissements (sans refus) : `MODULE_UNIQUEID` absent (identifiant = nom du fichier) ; identifiant ≠ nom du fichier ;
les 3 premières lignes utiles ne sont pas, dans l'ordre, `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`
(rétrocompatibilité avec la lecture par position de `GI`).
### 3.4 Contrôles et états (dans cet ordre, recalculés après chaque changement)
1. Identifiant en double (sandbox) : le second est `Refused` (« identifiant déjà utilisé par <dossier> »).
2. Kind Mod qui requiert un module de Kind Mod : `Refused` (« un mod ne peut pas requérir un autre mod (<id>) :
partager le composant dans une bibliothèque »).
3. Cycle dans les prérequis : tous les modules du cycle `Refused` (« cycle de prérequis : a → b → a »).
4. Prérequis introuvable : `Missing` (« prérequis absent : <id> ») ; prérequis présent mais non `Ready` ou désactivé :
`Missing` (« prérequis désactivé : <id> »). Un prérequis peut être un module du jeu (Data) : présent = satisfait.
5. Pas de fabrique (descripteur sandbox sans code) : `NoFactory` pour un mod ; une bibliothèque ou un système sans
fabrique est `Ready` (SB1 : services non encore extraits).
6. Sinon : `Ready` si activé, `Disabled` sinon. Système et bibliothèques sont toujours activés.
7. Si le mod ouvert n'est plus `Ready` : `Deactivate`, plus aucun mod ouvert (journal).
### 3.5 Ordre de `GetEntries()`
Système et bibliothèques par `Index` croissant (sans index : après, par identifiant) ; puis mods dans l'ordre persisté
(nouveaux en fin, par identifiant) ; puis modules du jeu par identifiant.
### 3.6 Journal `LogLPSandboxMods` (catégorie déclarée dans `LPSandboxModRegistry.h`, `LPSANDBOX_API`)
- `Display` au `Scan` : `Modules : <n> sandbox, <m> jeu ; ordre : <id>, <id>, …`
- `Display` par module : `<id> [<Kind>] <État> <raison>` ; `Warning` pour chaque avertissement de lecture et chaque refus.
- `Display` à chaque `SetEnabled`, `Move`, `Open` : `Ouvert : <id>` (preuve de chargement du nouveau code).
## 4. Persistance : `Saved/sandbox_mods.ini` (projet hôte)
```ini
[Mods]
Order=lps_couple,lps_interaction,lps_asset_integration
Open=lps_couple
[Enabled]
lps_couple=true
lps_interaction=true
lps_asset_integration=true
```
Lecture au `Scan` (`FConfigFile::Read`, comme `FLPSandboxConfig::LoadFile`), écriture après chaque changement
(`FConfigFile::Write`). Fichier absent : ordre par identifiant, cases = `bDefaultEnabled`, aucun mod ouvert mémorisé.
## 5. Enveloppes (`LPSandboxModWrappers.cpp`)
Trois classes `FLPSandboxModeWrapper : ILPSandboxMod` paramétrées par (Id, ModeLabel) :
| Id | ModeLabel |
|---|---|
| `lps_couple` | `Pair` |
| `lps_interaction` | `Interaction` |
| `lps_asset_integration` | `Mesure` |
- `Activate(Context)` : `Context.Player->OpenModeLabel(ModeLabel)`.
- `Deactivate` : rien (la transition, y compris `EndMeasure`, `SetAutoRun(false)`, `SetFreeView(false)` et la
restauration du bras caméra, est déjà faite par `SelectMode` quand le mode suivant s'ouvre).
- Fermeture sans mod suivant (mod ouvert désactivé) : `OpenModeLabel("Pair")` puis rangée d'animations masquée
(`Menu->AnimRow` réduit) : scène neutre.
- Enregistrement dans `FLPSandboxModule::StartupModule` : `RegisterFactory` ×3 puis `Scan()`.
## 6. Personnage (`ALPPairSandboxCharacter`)
- Ajouter `void OpenModeLabel(const FString& Label);` public : cherche `Label` dans `Menu->Modes` et appelle
`SelectMode` (privé, inchangé) ; journalise si introuvable.
- Le menu garde la liste interne `Menu->Modes` (Pair, Interaction, Mesure) mais **n'affiche plus** `ModeCombo`.
- Mod initial, à la fin de `ShowAnimMenu` (remplace le bloc actuel `sandbox_mode.txt` / `[Sandbox.Mode]`, même priorité
et même effacement de `sandbox_mode.txt`) :
1. `Saved/sandbox_mode.txt` présent : `Registry.Open(IdForModeLabel(contenu))` (pilote `AnimPipeline` : `Mesure`) ;
2. sinon `Open` persisté s'il est `Ready` ;
3. sinon `[Sandbox.Mode]` de `LPSandbox.ini` (défaut `Pair`) via `IdForModeLabel` ;
4. si le mod visé n'est pas `Ready` : premier mod `Ready` dans l'ordre ; aucun : scène neutre (§ 5).
- `ModeLabel` reste la vérité interne du mode ; le registre ne le modifie que par `OpenModeLabel`.
## 7. Barre latérale (`SLPSandboxSidebar`, T3)
- Remplace, dans `ShowAnimMenu`, le slot de `ModeCombo` (premier slot du `SVerticalBox`) ; le reste du panneau
(Mesure, MC, PNJ, animation, vue, filtres, aide) reste en dessous, inchangé.
- `SLATE_BEGIN_ARGS` : `_OnOpen` (`TFunction<void(const FString&)>`, le personnage appelle `Registry.Open(Id, Context)`).
- S'abonne à `Registry.OnChanged` et reconstruit ses lignes (désabonnement au destructeur).
- Section **Mods** (Kind Mod, non masqués) — une ligne : `SCheckBox` (désactivée si verrouillé) → `SetEnabled` ;
nom (`Name`) ; boutons ▲ ▼ → `Move(Id, -1/+1)` ; bouton ▶ (activé si `Ready`, libellé « ouvert » si `bOpen`) →
`OnOpen(Id)` ; si `Missing`/`Refused`/`NoFactory` : « ⚠ » avec infobulle `Reason`. Ligne du mod ouvert surlignée.
- Case **Afficher les modules masqués** (non persistée).
- Section **Système** (visible si la case est cochée) : `Index` en hexadécimal, `Id`, `Kind`, `Version`, état ; lecture seule.
- Section **Jeu (lecture)** (repliée par défaut, en-tête « Jeu : <n> modules ») : `Id`, `Name`, `Author`,
`Requirements`, avertissements de lecture en orange.
- Bouton **◀ / ▶** en tête du panneau : replie / déplie tout le panneau du menu (`Menu->Widget` réduit à la barre de titre).
- Style : polices et couleurs du menu actuel (`FCoreStyle::GetDefaultFontStyle("Regular", 14)` / 11 pour le détail),
largeur 300 comme les listes existantes.
## 8. Descripteurs (T3)
Sous `Plugins/LPSandbox/Modules/` ; trois premières lignes dans l'ordre `MODULE_UNIQUEID`, `MODULE_NAME`, `MODULE_AUTHOR`.
| Dossier | Id | Nom | Kind / clés | Prérequis |
|---|---|---|---|---|
| `System/0001_lps_system_core` | `lps_system_core` | Socle sandbox | `MODULE_SYSTEM: true` | — |
| `System/0002_lps_system_animmeasure` | `lps_system_animmeasure` | Chaîne de mesure (LPAnimMeasure) | `MODULE_SYSTEM: true` | `lps_system_core` |
| `System/0010_lps_lib_scene` | `lps_lib_scene` | Bibliothèque scène | (library par préfixe) | `lps_system_core` |
| `System/0011_lps_lib_actors` | `lps_lib_actors` | Bibliothèque personnages | (library) | `lps_system_core, lps_system_animmeasure` |
| `Mods/lps_couple` | `lps_couple` | Couple | `MODULE_DEFAULT_ENABLED: true` | `lps_system_core, lps_lib_scene, lps_lib_actors, lps_system_animmeasure` |
| `Mods/lps_interaction` | `lps_interaction` | Interaction | `MODULE_DEFAULT_ENABLED: true` | idem |
| `Mods/lps_asset_integration` | `lps_asset_integration` | Asset integration | `MODULE_DEFAULT_ENABLED: true` | `lps_system_core, lps_system_animmeasure` |
`MODULE_AUTHOR: LifePlay Revival`, `MODULE_VERSION: 0.1`, `MODULE_DESCRIPTION` d'une ligne.
## 9. Critères (repris par SB1-T5)
(a) chaque mod s'ouvre et se comporte comme l'ancien mode ; (b) couple → interaction → asset integration → couple ;
(c) mod décoché non ouvrable, et décocher le mod ouvert ramène la scène neutre ; (d) `Mods/lps_test_bad` (créé
pour l'essai puis retiré) requérant `lps_couple` → `Refused` avec raison ; (e) prérequis absent (renommer
temporairement `lps_lib_scene`) → `Missing` sur couple et interaction ; (f) ordre, cases et mod ouvert persistés après
relance ; (g) section Jeu : `.lpmod` du jeu lus, avertissements éventuels listés ; (h) pilote : `Saved/sandbox_mode.txt`
= `Mesure` + `Saved/sandbox_tour.txt` = `vignettes planches quitter` → tournée identique à AP7 (mesures, vignettes,
planches) ; (i) journal `LogLPSandboxMods` : 7 modules sandbox, ordre attendu, 0 refus hors essais.

View file

@ -0,0 +1,113 @@
# SB1-VAL — Validation automatique du registre de mods (SB1-T5, partie automatique)
**Fiche SB1-T5 — A-VAL — Modèle prévu sonnet | utilisé sonnet**
- Date : 2026-10-07. Build testé : celui de SB1-T4 (compilation code 0).
- Journaux copiés : `D:\Game\LifePlay-master 5.5\Saved\Logs\portage\SB1_VAL_E1.log`, `_E2`, `_E3`, `_E4`, `_E4b`, `_E5`.
- Procédure : lancement par `LifePlay_pair_sandbox.bat`, attente des lignes `LogLPSandboxMods` + `Ouvert :`, copie du journal, arrêt du seul `UnrealEditor` lancé (E5 : le pilote lance et ferme lui-même).
## Tableau des essais
| Essai | Attendu | Observé | Verdict |
|---|---|---|---|
| E1 démarrage (g, i) | 7 modules sandbox ; ordre système puis mods ; aucun refus ni prérequis manquant ; `.lpmod` du jeu lus ; mod ouvert ; `sandbox_mods.ini` créé | `Modules : 7 sandbox, 16 jeu ; ordre : lps_system_core, lps_system_animmeasure, lps_lib_scene, lps_lib_actors, lps_asset_integration, lps_couple, lps_interaction` ; les 7 en `pret` ; aucun `Warning` de lecture (`LogLifePlay: Modules : 16 .lpmod lu(s), 0 avec MODULE_GAME_VERSION`) ; `Ouvert : lps_couple`. `sandbox_mods.ini` : `Order=lps_asset_integration,lps_couple,lps_interaction`, `Open=lps_couple`, 3 cases `true` | PASS |
| E2 mod requérant un mod (d) | `lps_test_bad` refusé, 3 mods prêts | `Modules : 8 sandbox …, lps_test_bad` ; `lps_test_bad [mod] refuse : un mod ne peut pas requerir un autre mod (lps_couple) : partager le composant dans une bibliotheque` (Display + Warning) ; les 3 mods `pret` ; `Ouvert : lps_couple` | PASS |
| E3 prérequis absent (e) | couple et interaction « prérequis manquant », asset_integration prêt | `Modules : 6 sandbox` ; `lps_couple [mod] prerequis manquant : prerequis absent : lps_lib_scene` ; idem `lps_interaction` ; `lps_asset_integration [mod] pret`. Le `Open=lps_couple` persisté est tenté puis refusé : `Ouverture refusee : lps_couple (prerequis absent : lps_lib_scene)` puis `Ouvert : lps_asset_integration` (repli sur le premier mod prêt, § 6.4 de la SPEC) | PASS |
| E4 persistance (f) | ordre du fichier, `lps_couple` désactivé, `Ouvert : lps_interaction` | ordre `…, lps_asset_integration, lps_couple, lps_interaction` ; `lps_couple [mod] desactive` ; `Ouvert : lps_interaction` | PASS (*) |
| E4b (essai ajouté) ordre non alphabétique | l'ordre du fichier prime | avec `Order=lps_interaction,lps_asset_integration,lps_couple` : `ordre : …, lps_lib_actors, lps_interaction, lps_asset_integration, lps_couple` | PASS |
| E5 non-régression Mesure (h) | tournée identique, mêmes fichiers, `Ouvert : lps_asset_integration` | `AnimPipeline.ps1 -Pack freeanims -From 5 -To 5` : `verdict : ok (435 s)`, `Chaine terminee`, code de sortie 0 ; journal `SB1_VAL_E5.log` : `Ouvert : lps_asset_integration` (ordre `lps_couple, lps_interaction, lps_asset_integration`) ; 32 fichiers de même noms (10 `vignettes\*.png`, 10 `planches\*.jpg`, 10 `planches\*.json`, 2 `retarget_pose_*.txt`) ; 10 `planches\*.json` identiques ; `eval_Bridget.csv` / `eval_Michael.csv` identiques (SHA-256) ; 20 images (10 png, 10 jpg) d'empreinte différente | PASS pour les fichiers et JSON ; images : voir ci-dessous |
(*) L'ordre demandé en E4 (`lps_asset_integration, lps_couple, lps_interaction`) est aussi l'ordre alphabétique par défaut : il ne prouve pas à lui seul la lecture du fichier ; E4b le prouve.
### E5 — images dont l'empreinte diffère (tailles en octets, référence → nouveau)
Tous les noms et les `.json` sont identiques ; écarts de taille faibles (rendu non déterministe attendu), à juger par l'utilisateur.
| Fichier | Référence → nouveau |
|---|---|
| planches\AS_AirKiss.jpg | 148582 → 148574 |
| planches\AS_AttackInLevitation2.jpg | 139248 → 139244 |
| planches\AS_ChillDriving.jpg | 187677 → 187692 |
| planches\AS_ComboHands.jpg | 159898 → 159913 |
| planches\AS_EatingOnParty.jpg | 141183 → 141291 |
| planches\AS_FlyingUp.jpg | 119016 → 119041 |
| planches\AS_GoForward.jpg | 150693 → 150742 |
| planches\AS_Rest.jpg | 138519 → 138633 |
| planches\AS_RightLegKick2.jpg | 88070 → 88116 |
| planches\AS_Talk.jpg | 144361 → 144274 |
| vignettes\AS_AirKiss.png | 261580 → 261353 |
| vignettes\AS_AttackInLevitation2.png | 253439 → 253838 |
| vignettes\AS_ChillDriving.png | 289382 → 289345 |
| vignettes\AS_ComboHands.png | 268915 → 270145 |
| vignettes\AS_EatingOnParty.png | 263792 → 263826 |
| vignettes\AS_FlyingUp.png | 263466 → 263846 |
| vignettes\AS_GoForward.png | 256944 → 256957 |
| vignettes\AS_Rest.png | 242586 → 242608 |
| vignettes\AS_RightLegKick2.png | 265358 → 265512 |
| vignettes\AS_Talk.png | 260640 → 259551 |
Référence : `D:\Game\LifePlay_Backup_5.5\SB1-VAL\reference_freeanims\` (+ `eval_*.csv` avant). Nouveau : `D:\Game\LifePlay-master 5.5\Saved\AnimPipeline\freeanims\sandbox\`.
## Observations
- Le fichier `sandbox_mods.ini` n'existait pas avant E1 (créé au premier lancement avec l'ordre alphabétique, conforme à la SPEC § 4).
- E2 a inscrit `lps_test_bad` dans l'ini (Order et Enabled) ; E3 et E5 ont modifié `Open` (le mod ouvert est mémorisé à chaque ouverture, dont celle de la tournée Mesure : `Open=lps_asset_integration` après E5). Le fichier a été remis à l'état demandé.
- Le pilote (étape 5) lance `LifePlay_interaction_sandbox.bat` et non `LifePlay_pair_sandbox.bat`.
- Aucun avertissement de lecture sur les 16 `.lpmod` du jeu (le registre ne journalise pas les modules du jeu un par un ; seul le total est donné).
## État final des fichiers remis en place
- `Test-Path ...\Modules\Mods\lps_test_bad` : False (dossier supprimé).
- `Test-Path ...\Modules\System\0010_lps_lib_scene\lps_lib_scene.lpmod` : True ; `D:\Game\LifePlay_Backup_5.5\SB1-VAL\0010_lps_lib_scene` : absent (déplacé retour).
- Aucun processus `UnrealEditor` actif à la fin.
- `D:\Game\LifePlay-master 5.5\Saved\sandbox_mods.ini` :
```
[Mods]
Order=lps_couple,lps_interaction,lps_asset_integration
Open=lps_couple
[Enabled]
lps_couple=true
lps_interaction=true
lps_asset_integration=true
```
- `Saved\sandbox_mode.txt` : `Pair` (remis par le pilote). `git status` de l'addon : aucun fichier parasite sous `Modules\`.
- Aucun source, `.lpmod` existant ni script modifié ; aucun commit.
## À contrôler visuellement par l'utilisateur
- (a) Chaque mod (Couple, Interaction, Asset integration) s'ouvre depuis la barre latérale et se comporte comme l'ancien mode.
- (b) Enchaînement couple → interaction → asset integration → couple sans résidu (caméra, vue libre, animation automatique).
- (c) Un mod décoché n'est pas ouvrable (▶ inactif) ; décocher le mod ouvert ramène la scène neutre.
- Aspect de la barre latérale : sections Mods, Système (case « modules masqués »), Jeu (16 modules, repliée), bouton ◀ / ▶, ⚠ avec infobulle.
- Images E5 qui diffèrent : les 20 fichiers du tableau ci-dessus (comparer vignettes et planches avec la référence).
## Verdict partiel
**PASS** de la partie automatique (E1 à E5 : critères d, e, f, g, h, i satisfaits ; images E5 : écarts de pixels à juger visuellement). Critères a, b, c restent à contrôler par l'utilisateur.
## Contrôle visuel utilisateur (2026-10-07)
| Critère | Verdict |
|---|---|
| (a) chaque mod s'ouvre depuis la barre latérale et se comporte comme l'ancien mode | PASS (« pas de défauts constatés ») |
| (b) enchaînement couple, interaction, asset integration, couple sans résidu | PASS (« pas de défauts constatés ») |
| (c) mod décoché non ouvrable ; décocher le mod ouvert ramène la scène neutre | PASS (« pas de défauts constatés ») |
| Aspect de la barre latérale | PASS (« pas de défauts constatés ») |
| Images E5 (20 fichiers qui diffèrent) | PASS (« pas de défauts constatés ») |
## Verdict global du lot SB1
**PASS** (partie automatique E1 à E5 + contrôle visuel utilisateur). Commits addon : `82dae58380b76c45f60541fd7cb5a0a409700e10` (code) puis commit de pin README ; tag `lot-SB1-valide`.
## Modèles par fiche
| Fiche | Modèle prévu | Modèle utilisé |
|---|---|---|
| SB1-T1 | opus | opus |
| SB1-T2 | opus | opus |
| SB1-T3 | sonnet | sonnet |
| SB1-T4 | haiku | haiku |
| SB1-T5 | sonnet | sonnet |
| SB1-T6 | sonnet | sonnet |