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

14 KiB
Raw Blame History

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)

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

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 »).
  2. Kind Mod qui requiert un module de Kind Mod : Refused (« un mod ne peut pas requérir un autre mod () : 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 : ») ; prérequis présent mais non Ready ou désactivé : Missing (« prérequis désactivé : »). 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)

[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 : 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.