LifePlay-Revival/Docs/Migration/etat/sandbox/SB1/SB1-SPEC.md
2026-10-07 15:50:03 +02:00

244 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.