LifePlay-Revival/Docs/23_Cycle_De_Vie_Mod.md
edujiro5972 8084622450 LifePlay UE 5.8 : état de développement au 2026-10-19
Tickets : REP1-T4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 22:57:42 +02:00

503 lines
40 KiB
Markdown
Raw Permalink 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.

# 23. Cycle de vie d'un mod (2026-10-07)
> Depuis le lot DC3 (2026-10-07), ces scripts, `ModSources/` et `AnimSources/` sont dans le dépôt addon `LifePlay-Sandbox` (même disposition relative ; lancer depuis l'addon, `D:\Game\LifePlay-Sandbox`).
Référence pour les développeurs du jeu et des outils de mods : ce qu'est un mod LifePlay, comment il est fabriqué,
livré, découvert, activé et consommé, quelles propriétés sont réellement lues, et ce qui pourrait évoluer.
Constats relevés dans le code du jeu, des Blueprints (`GI`) et du moteur UE 5.5 installé
(`Engine/Source/Runtime/Projects`) le 2026-10-07. Guide des moddeurs (anglais) :
`Docs/Modding/FullGuides/Creating_Asset_Mods.md`.
Documents liés : [01](01_Architecture_Initiale.md) (données texte, `GI`), [15](15_Modules_Optionnels.md) (modules
livrés à part), [16](16_H3_Mods_Paquets.md) (mods d'assets H3), [17](17_Mods_Ecran_Titre.md) (gestionnaire de mods
depuis l'écran titre), [20](20_Chaine_Animations.md) (packs d'animations AP1–AP7).
## 1. Deux moitiés, deux mécanismes
Un mod LifePlay est un **module texte** du jeu, éventuellement accompagné d'un **plugin de contenu** Unreal.
| | Module LifePlay (texte) | Plugin de contenu (assets cuits) |
|---|---|---|
| Emplacement installé | `LifePlay/Content/Modules/<id>/` | `LifePlay/Mods/<id>/` |
| Descripteur | `<id>.lpmod` (`CLÉ: valeur`) | `<id>.uplugin` (JSON) |
| Contenu | `.lpaction`, `.lpscene`, `.lpstat`, `.lpquest`, `.lptalk`, `.lpdesc`, `.lpai`, `.lpcharacter`, `.lpworld`, `.lphair`, tables, crédits, catalogue | `Content/Paks/Windows/<id>LifePlay-Windows.pak/.utoc/.ucas` (IoStore, `AssetRegistry.bin` du mod dans le `.pak`) |
| Lu par | `ModdingExtra` (`LoadTxt`, `LPDataCache`), Blueprint `GI`, sous-systèmes C++ | Gestionnaire de plugins du moteur (montage), registre d'assets |
| Activation | case du gestionnaire de mods (`GI.ModEnableds`) | toujours monté s'il est présent (plugin `Mod` activé par défaut) |
| Existe depuis | jeu d'origine (Vinfamy) | lot H3 (2026-10-03) |
Mods texte seuls : `vin_*`. Mods d'assets : `lpr_hair_pocolov` (coiffures), `lpr_anim_quaternius` et
`lpr_anim_freeanims` (animations). Les deux moitiés portent le **même identifiant** `<id>` : `MODULE_UNIQUEID` du
`.lpmod` = nom du plugin = point de montage `/<id>/` des assets.
## 2. Cycle de vie
```
FABRICATION (projet source) LIVRAISON JEU (à chaque démarrage)
───────────────────────── ───────── ────────────────────────
Tools/NewMod.ps1 → ModSources/<id>/ <id>-<v>-for-LifePlay-<jeu>.zip
.uplugin, Module/<id>.lpmod, CREDITS LifePlay/Mods/<id>/… 1. moteur : découverte Mods/, montage
import des assets (HairPipeline, LifePlay/Content/Modules/<id>/ 2. registre d'assets : AssetRegistry.bin
AnimPipeline → DeliverAnims) README.txt 3. GI : LoadSettings → ModEnableds
Tools/BuildMod.ps1 -Name <id> → removeDeletedMods → PopulateRaws
cuisson DLC -basedonreleaseversion installation : extraire 4. sous-systèmes C++ : métadonnées,
contrôle du conteneur, archive dans le dossier du jeu coiffures, animations, avertissement
désinstallation : supprimer 5. gestionnaire de mods : cocher / décocher
les deux dossiers → PopulateRaws, SaveModSelection
```
### 2.1 Fabrication
| Étape | Outil | Résultat |
|---|---|---|
| Squelette | `Tools/NewMod.ps1 -Name <id> [-Hairs]` | `ModSources/<id>/<id>.uplugin`, `Module/<id>.lpmod` (trois premières lignes imposées, voir § 3.2), `CREDITS.txt` |
| Édition | `Tools/EditMod.ps1`, `Tools/ModSource.ps1` | le mod est déplacé dans `<projet>/Mods/` le temps du travail, puis remis dans `ModSources/` : le jeu de base n'en contient jamais rien |
| Assets : coiffures | `Tools/HairPipeline.ps1 -Config Tools/mods/<id>.json` | `/<id>/Hairs/…` + fichiers `.lphair` |
| Assets : animations | `Tools/anim/AnimPipeline.ps1` (`BuildMod.ps1 -Anim <pack>`), étape 6 `DeliverAnims` | `/<id>/Anims/Female/<clé>_F`, `/<id>/Anims/Male/<clé>_M`, `.lpmod` et `CREDITS.txt` tirés de `AnimSources/<pack>/pack.json`, catalogue, vignettes, planches |
| Cuisson | `Tools/BuildMod.ps1 -Name <id> -Version <jeu>` | cuisson DLC du seul plugin contre `Releases/<jeu>` ; refus si un autre plugin est dans `Mods/`, si une entrée du conteneur sort de `Mods/<id>/`, si un mod payant cite une licence non commerciale |
| Archive | `BuildMod.ps1` | `<id>-<version>-for-LifePlay-<jeu>.zip` testée ; le `.lpmod` livré reçoit `MODULE_PACKAGED` et `MODULE_GAME_VERSION`, le `.uplugin` livré `VersionName` et `CreatedByURL` |
Contrainte de compatibilité : un conteneur de mod est cuit **contre une version de référence précise** du jeu
(`Releases/<version>`, produite par `-createreleaseversion` à chaque publication). Les assets de base qu'il référence
(squelettes Genesis, matériau `DazHair`, paramètres de compression du projet) ne doivent plus être renommés ni déplacés.
Un mod ne doit référencer aucun contenu `/Engine/` non cuit par le jeu (cas des clips UE5 en AP7 : compression remise
au défaut du projet par `DeliverAnims.py`).
### 2.2 Installation et désinstallation
Le joueur extrait l'archive dans le dossier du jeu (celui de `LifePlay.exe`), en fusionnant le dossier `LifePlay`.
Pour désinstaller : supprimer `Content/Modules/<id>` **et** `Mods/<id>`. Aucun outil ne vérifie aujourd'hui qu'une
moitié n'a pas été laissée seule (voir § 6).
### 2.3 Démarrage du jeu
| Ordre | Qui | Ce qui se passe |
|---|---|---|
| 1 | Moteur, `FPluginManager::ReadAllPlugins` | lit **toujours** `<jeu>/Mods/` ; chaque `Mods/<id>/<id>.uplugin` devient un plugin de type `Mod`, activé par défaut (`FPlugin::IsEnabledByDefault` : chargé « depuis le projet ») |
| 2 | Moteur | `CanContainContent: true` : le point de montage `/<id>/` est ajouté, les conteneurs `Content/Paks/Windows/*` sont montés |
| 3 | Registre d'assets | ajoute le `AssetRegistry.bin` de chaque plugin de contenu : les assets du mod sont trouvables par chemin et par classe sans être chargés |
| 4 | `ULPModInfoSubsystem::Initialize` (C++) | lit chaque `Modules/<id>/<id>.lpmod` (toutes les clés), retient `MODULE_GAME_VERSION` ; journal `Modules : N .lpmod lu(s)` |
| 5 | `ULPModDailiesSubsystem::Initialize` (C++, AP5) | relève les `UAnimSequence` sous `/<plugin>/Anims` de chaque plugin `Mod` monté ; clé = nom sans suffixe `_F`/`_M` |
| 6 | `GI.LoadSettings` (Blueprint) | `GameSettings.sav` → `ModEnableds` ; `ReadModLauncher` (`Modules/ModLauncher.config`, absent en pratique) ; `removeDeletedMods` retire de la liste les modules dont le `.lpmod` n'existe plus |
| 7 | `ULPHairstyleSubsystem` (C++) | après `LoadSettings`, ajoute cochés les modules `MODULE_DEFAULT_ENABLED: true` absents de la liste ; relève les `.lphair` |
| 8 | `GI.PopulateRaws` (Blueprint) | pour **chaque module coché** : lecture des fichiers texte (`LoadTxt`, cache `LPDataCache`) → actions, scènes, stats, quêtes, dialogues, mondes |
| 9 | `ULPModDailiesSubsystem::Tick` | dès que `GI.Room` existe : ajoute les clips de mod à `Room.Dailies` (une fois par pièce) |
| 10 | `ULPModInfoSubsystem::Tick` (toutes les 2 s) | module coché dont `MODULE_GAME_VERSION` ≠ `ProjectVersion` : fenêtre « Mod version warning », une fois par module et par session |
### 2.4 Activation et désactivation
- État : tableau `GI.ModEnableds` de structures `ModEnabled` (`UniqueID`, `IsEnabled`). Un module **absent** de la
liste est inactif (`GI.IsModEnabled`, `ULPModInfoSubsystem::IsModuleEnabled`, `ULPHairstyleSubsystem::IsModuleEnabled`).
- Gestionnaire de mods (`UI/ModManager`) : en partie (entrée d'origine) ou depuis l'écran titre (lot UI15). À la
fermeture : `PopulateRaws` (données relues avec les modules choisis) ; depuis l'écran titre, le choix est écrit dans
`GameSettings.sav` par `ULPModSettingsLibrary::SaveModSelection` (seul `ModEnableds` est remplacé).
- Effet de la case selon la nature du contenu :
| Contenu | Décoché → |
|---|---|
| Données texte (actions, scènes, stats…) | non lues par `PopulateRaws` : absentes du jeu |
| Coiffures (`.lphair`) | retirées des listes (`LPHairstyleSubsystem`) ; paquet toujours monté, inutilisé |
| Animations (`/<id>/Anims`) | **toujours ajoutées** à `Room.Dailies` : `LPModDailies` ne consulte pas la case (§ 6, E1) |
| Avertissement de version | non affiché |
| Plugin Unreal | toujours monté : le moteur ignore `ModEnableds` |
Ordre de chargement (relevés `LPDumpFunction` de `GI` et `UI/ModuleTick`) :
- `GI.ModEnableds` est ordonné. Les boutons monter / descendre de chaque ligne du gestionnaire (`UI/ModuleTick`)
appellent l'événement `GI.SortLoadOrder(ForUp, ModuleIndex)`, qui échange deux éléments de `ModEnableds`
(`Array_Swap`) puis reconstruit la liste (`AddTempModuleTicks`). `GI.ToggleMod(Mod, IsEnabled)` parcourt
`ModEnableds` et remplace l'élément dont `UniqueID` correspond.
- L'affichage est inversé par rapport au tableau : indice dans `ModEnableds` = longueur − 1 − position affichée.
Monter vers la position affichée 0 est refusé (garde `ModuleIndex == 1 && ForUp == 1`), de même qu'un indice hors bornes.
- Effet (`PopulateRaws`, événement de `GI`) : boucle sur `ModEnableds` du début à la fin, modules cochés seulement.
Pour chaque type (`lpstat`, `lpaction`, `lpscene`, `lptalk`, `lpdesc`, `lpquest`, `lpai`), les fichiers de
`Modules/<id>` sont lus (`GetAllFilesFromFolder`) et un fichier est ignoré si **son nom de fichier** (partie après
le dernier `/`) a déjà été lu (`FilenamesUsed`). Le premier lu gagne : le module le plus bas dans la liste
affichée l'emporte, ce que dit le guide d'origine (`Docs/Modding/FullGuides/Getting_Started.txt` l. 27 : « the one
lowest in the load order wins »).
- Nuance : la comparaison porte sur le nom de fichier seul, pas sur le chemin (le guide dit « same file in the same place »).
- Réserve : le relevé n'affiche pas le nom des macros de boucle, et `GI` contient aussi une `ReverseForEachLoop`.
Le sens de la boucle est déduit de la cohérence avec l'affichage inversé et avec le guide ; à confirmer par un
relevé qui nomme les macros (§ 6, E15).
- Les animations et les assets ne suivent pas cet ordre (seuls les fichiers texte lus par `PopulateRaws` le suivent).
Verrouillage de `vin_Base` : il est en dur dans `UI/ModuleTick`. La case n'appelle `GI.ToggleMod` que si
`UniqueID != 'vin_Base'` ; pour `vin_Base`, les boutons monter / descendre sont masqués (`SetVisibility(Hidden)`).
Aucune clé `.lpmod` ne permet de verrouiller un autre module.
### 2.5 Consommation des assets
| Type | Déclaration côté texte | Résolution | Code |
|---|---|---|---|
| Coiffure | `.lphair` : `HAIRSTYLE: /<id>/Hairs/X.X` | registre d'assets, repli `FPackageName::DoesPackageExist` | `LPHairstyleSubsystem` |
| Animation | `ANIMATION: <clé>` (`.lpaction`), `Actor.animate(<clé>)` (`.lpscene`) | `Person::PlayDaily` : clés en dur, sinon (correctif `LPPatchDailies`) `IsModDaily(<clé>)` → `PlayDailyAnim` parcourt `Room.Dailies`, suffixe `_F`/`_M` selon le sexe | `LPModDailies`, `Person.uasset` patché |
Convention des clés d'animation : `<préfixe du pack>_<clip>` en minuscules (`qua_dance_loop`, `fap_as_talk`), assets
`<clé>_F` et `<clé>_M`. Une clé inconnue ne joue rien (journal `PlayDaily(<clé>) -> cle inconnue` hors Shipping).
### 2.6 Schémas du cycle de vie
Schémas tirés des relevés `LPDumpFunction` de `GI` (graphe principal, 6 199 nœuds) et de `UI/ModuleTick`, du code C++
du jeu et du moteur (`GameInstance.cpp`, `PluginManager.cpp`). Les noms entre parenthèses sont les paramètres des
fonctions ou des événements. `GI.X` désigne un événement ou une fonction du Blueprint `GI` ; `ULP…` une classe C++ du jeu.
#### 2.6.1 États d'un module
```mermaid
stateDiagram-v2
[*] --> Installe : extraction de l'archive
Installe : Installé (fichiers sur disque)
Installe --> Monte : démarrage, FPluginManager (Mods/id)
Monte : Plugin monté (assets /id/ visibles)
Monte --> Liste : GI.LoadSettings, ULPModInfoSubsystem
Liste : Listé (.lpmod lu)
Liste --> Inscrit : ReadModLauncher ou GameSettings.sav, MODULE_DEFAULT_ENABLED
Inscrit : Inscrit dans GI.ModEnableds
Inscrit --> Actif : IsEnabled = true
Inscrit --> Inactif : IsEnabled = false
Actif --> Inactif : GI.ToggleMod(Mod, false)
Inactif --> Actif : GI.ToggleMod(Mod, true)
Actif : Actif (textes lus par GI.PopulateRaws)
Inactif : Inactif (textes ignorés, plugin toujours monté)
Liste --> Retire : dossier Modules/id supprimé
Retire : Retiré de ModEnableds (GI.removeDeletedMods)
Retire --> [*]
Actif --> Decharge : fermeture du jeu
Inactif --> Decharge : fermeture du jeu
Decharge : Déchargé (sous-systèmes Deinitialize, conteneurs démontés)
Decharge --> [*]
```
`vin_Base` ne peut pas passer à l'état Inactif : la case de `UI/ModuleTick` n'appelle `GI.ToggleMod` que si
`UniqueID != 'vin_Base'`.
#### 2.6.2 Démarrage : séquence
```mermaid
sequenceDiagram
autonumber
participant M as Moteur (FPluginManager)
participant R as Registre d'assets
participant GI as GI (Blueprint)
participant S as Sous-systèmes C++
participant D as Disque Modules/
M->>M: ReadAllPlugins : Mods/id/id.uplugin devient un plugin de type Mod
M->>M: ConfigureEnabledPlugins (dépendances Plugins, obligatoire absente = arrêt)
M->>R: CanContainContent : montage de /id/, MountPak(pak, 0)
R->>R: AssetRegistry.bin du mod ajouté
M->>GI: UGameInstance::Init puis ReceiveInit (minuterie 2 s)
M->>S: SubsystemCollection.Initialize
S->>D: ULPModInfoSubsystem lit Modules/*/id.lpmod
S->>R: ULPModDailiesSubsystem relève les UAnimSequence sous /id/Anims
GI->>GI: DelayedStart puis LoadSettings(LoadLastSave)
GI->>D: ReadModLauncher, ParseConfig(Content), ModEnableds si UseConfig
GI->>D: GameSettings.sav donne ModEnableds, puis removeDeletedMods
S->>GI: ULPHairstyleSubsystem ajoute les modules MODULE_DEFAULT_ENABLED
GI->>D: PopulateRaws lit les textes des modules cochés (LoadTxt, LPDataCache)
S->>GI: ULPModDailiesSubsystem::Tick ajoute les clips à Room.Dailies
S->>GI: ULPModInfoSubsystem::Tick (2 s) : avertissement MODULE_GAME_VERSION
```
#### 2.6.3 Arbre logique de `GI.LoadSettings(LoadLastSave)`
```mermaid
flowchart TD
A["GI.LoadSettings(LoadLastSave)"] --> B["ThemeSong.SetActive(false), r.Streaming.PoolSize 1500, EnableDynamicResolution"]
B --> C{"DoesSaveGameExist('GameSettings', 0)"}
C -- oui --> D["LoadGameFromSlot('GameSettings', 0), cast GameSettings"]
D --> E["ModEnableds = GameSettings.ModEnableds"]
E --> F["GI.ReadModLauncher()"]
F --> G["GI.removeDeletedMods()"]
G --> H["environ 60 réglages copiés (DifficultyPlayerInput, SceneFrequency, MarkerSize...)"]
H --> I["loadAverageAttractiveness()"]
I --> J["DefaultDistributionIfNeeded()"]
J --> K["GI.PopulateRaws()"]
C -- non --> F2["GI.ReadModLauncher()"]
F2 --> K
K --> L{"LoadLastSave et réglages existants"}
L -- oui --> M["LoadThis = LastSaveGame"]
F -.-> F1["ParseConfig(Content = ModLauncher.config) : UseConfig, ModEnableds"]
F1 -.-> F1b{"UseConfig"}
F1b -. oui .-> F1c["ModEnableds = liste du fichier"]
```
`Modules/ModLauncher.config` est absent en pratique : `UseConfig` est faux et `ModEnableds` vient de
`GameSettings.sav` (ou de la valeur par défaut du Blueprint au premier lancement).
#### 2.6.4 Arbre logique de `GI.PopulateRaws()`
```mermaid
flowchart TD
P["GI.PopulateRaws()"] --> P1["FillNamePool, readUniforms, PopulateAllMorphTags, PopulateBeautyStandardsDefault, FillPregnancyMorphs"]
P1 --> P2["FilenamesUsed.Clear()"]
P2 --> L{"Pour chaque ModEnabled (UniqueID, IsEnabled) de ModEnableds, dans l'ordre"}
L -- "IsEnabled faux" --> L
L -- "IsEnabled vrai" --> T["Pour chaque type : lpstat, lpaction, lpscene, lptalk, lpdesc, lpquest, lpai"]
T --> F["GetAllFilesFromFolder('Modules/' + UniqueID, Ext)"]
F --> N{"nom de fichier (après le dernier '/') déjà dans FilenamesUsed ?"}
N -- oui --> X["ignoré : le module lu avant l'emporte"]
N -- non --> Y["LoadTxt vers Raws_Stat, Raws_Action, Raws_Scene, Raws_Talks, Raws_Descs, Raws_Quests, AI_High, AI_Med ; FilenamesUsed.Add(nom)"]
L -- fin --> Q["PopulateStats, PopulateActions, PopulateScenes, PopulateQuests, PopulateTalksAndDescs, PopulateWorldPresets, RemoveNPCOnly, CleanRaws"]
Q --> R["IsModEnabled(...) : options liées à un module (Datings...)"]
```
Le premier fichier lu l'emporte. Comme l'affichage du gestionnaire est inversé (§ 2.4), c'est le module le plus bas
de la liste affichée. Les `.lpscene` passent en plus deux conditions sur leur contenu avant d'être ajoutées.
#### 2.6.5 Gestionnaire de mods : ouverture, actions, fermeture
```mermaid
flowchart TD
O["Menu en partie : HideUI() puis GI.CreateModManagerWidget(GameStart)"] --> O2["CreateWidget ModManager, GameStart mémorisé, AddToViewport(0)"]
O1["Écran titre (UI15) : GI.CreateModManagerWidget(GameStart = false), ModManager.TitleMenu = menu"] --> O2
O2 --> O3["TempModuleTicks.Clear(), TempModules.Clear()"]
O3 --> O4["LPMods = GetAllFilesFromFolder('Modules', 'lpmod')"]
O4 --> O5{"Pour chaque .lpmod : UniqueID déjà dans TempModules ?"}
O5 -- non --> O6["TempModules.Add(UniqueID) ; CreateWidget ModuleTick (FullString = fichier entier, UniqueID)"]
O6 --> O7["CheckIfModAlreadyFound(UniqueID, ModuleTick) : AlreadyExisted"]
O7 --> O8["NameText, AuthorText depuis MODULE_NAME, MODULE_AUTHOR ; TempModuleTicks.Add"]
O5 -- fin --> O9["removeDeletedMods(), AddTempModuleTicks(), SetInputMode_UIOnly(ModManager)"]
O9 --> U{"Action du joueur dans UI/ModuleTick"}
U -- "case cochée ou décochée" --> T1{"UniqueID différent de 'vin_Base'"}
T1 -- oui --> T2["GI.ToggleMod(Mod = UniqueID, IsEnabled) : remplace l'élément de ModEnableds"]
T1 -- non --> T3["sans effet"]
U -- "monter ou descendre" --> S1["GI.SortLoadOrder(ForUp = 1 ou -1, ModuleIndex = position affichée)"]
S1 --> S2{"ModuleIndex = 1 et ForUp = 1, ou hors bornes ?"}
S2 -- non --> S3["Array_Swap(ModEnableds, L-1-ModuleIndex, L-1-ModuleIndex+ForUp), ClearChildren, AddTempModuleTicks"]
S2 -- oui --> S4["refusé"]
U -- "fermer, en partie" --> C1["GI.CloseModManager(GameStart) : ShowUI, ClearModManagerWidget, PopulateRaws, ModManagerOn = false"]
C1 --> C2{"GameStart"}
C2 -- oui --> C3["CharGenInput(GameStart = true)"]
C2 -- non --> C4["DelayThenFollowUp()"]
U -- "fermer, écran titre (UI15)" --> C5["menu titre réaffiché, ClearModManagerWidget, PopulateRaws, ULPModSettingsLibrary::SaveModSelection"]
```
#### 2.6.6 Appel d'une animation de mod
```mermaid
sequenceDiagram
participant Sc as Scène ou action
participant GI as GI.CallFunction
participant P as Person (Blueprint patché)
participant L as ULPModDailiesLibrary
participant Ro as Room.Dailies
Sc->>GI: Actor.animate(clé) ou ANIMATION: clé
GI->>P: PlayDaily(clé), cas 'animate'
P->>P: SwitchString des clés du jeu (dance...)
P->>L: cas Default : IsModDaily(clé)
alt clé de mod
L-->>P: vrai (journal « cle de mod » hors Shipping)
P->>Ro: PlayDailyAnim parcourt Room.Dailies, suffixe _F ou _M selon IsMale
Ro-->>P: UAnimSequence /id/Anims/.../clé_F ou clé_M
P->>P: currentDaily joué par l'AnimBP Daily ou Daily_F
else clé inconnue
L-->>P: faux (journal « cle inconnue ») : rien n'est joué
end
```
La case du gestionnaire n'intervient pas dans cet arbre (§ 6, E1).
#### 2.6.7 Désactivation, désinstallation et déchargement
```mermaid
flowchart TD
A["Module décoché : GI.ToggleMod(Mod, false)"] --> B["fermeture du gestionnaire : GI.PopulateRaws()"]
B --> B1["textes du module non relus : actions, scènes, stats absentes"]
A --> C["ULPHairstyleSubsystem::IsModuleEnabled : coiffures filtrées à la demande"]
A --> D["ULPModInfoSubsystem : plus d'avertissement de version"]
A --> E["ULPModDailiesSubsystem : clips toujours dans Room.Dailies (E1)"]
A --> F["plugin /id/ toujours monté jusqu'à la fermeture"]
G["Dossier Modules/id supprimé"] --> H["démarrage suivant : GI.removeDeletedMods() retire l'entrée de ModEnableds"]
I["Dossier Mods/id supprimé"] --> J["démarrage suivant : plugin absent, clips et coiffures du mod introuvables"]
K["Fermeture du jeu"] --> K1["UGameInstance::Shutdown, ReceiveShutdown"]
K1 --> K2["SubsystemCollection.Deinitialize : LPModDailies retire son ticker et vide les clés ; LPModInfo retire son ticker et ferme l'avertissement"]
K2 --> K3["moteur : modules et conteneurs démontés à la sortie"]
```
Il n'existe pas de déchargement à chaud : un plugin de mod reste monté du démarrage à la fermeture, quel que soit
l'état de sa case.
## 3. Propriétés réellement lues
### 3.1 `<id>.uplugin` (moteur)
| Propriété | Lue | Effet |
|---|---|---|
| nom du fichier / dossier | oui | nom du plugin, point de montage `/<id>/` |
| `CanContainContent` | oui | **indispensable** : sans elle, le conteneur n'est pas monté |
| `EnabledByDefault` | oui (absente) | absente : plugin `Mod` activé |
| `ExplicitlyLoaded` | oui (absente) | absente : monté au démarrage |
| `Modules` | oui (absente) | aucun code : un mod Shipping ne peut pas en apporter (pas de binaire compatible) |
| `Plugins` : `[{ "Name": "...", "Enabled": true, "Optional": false }]` | oui, appliqué | dépendance obligatoire absente : la configuration des plugins échoue et le jeu ne démarre pas (`LaunchEngineLoop.cpp` renvoie `false` après `LoadModulesForEnabledPlugins`) ; `"Optional": true` : ignorée si absente |
| `Hidden` | oui | utilisé seulement par le navigateur de plugins de l'éditeur ; sans effet en jeu |
| `Sealed` | oui | interdit qu'un autre plugin en dépende |
| `DisallowedPlugins` | oui | interdit une dépendance donnée |
| `SupportedTargetPlatforms`, `EngineVersion` | oui si présentes | plateformes ; `EngineVersion` ne fait qu'avertir |
| `FriendlyName`, `Description`, `Category`, `CreatedBy`, `CreatedByURL`, `Version`, `VersionName`, `IsBetaVersion`, `Installed` | non en jeu | descriptifs (navigateur de plugins de l'éditeur) |
### 3.2 `<id>.lpmod` (jeu)
| Clé | Lue par | Effet |
|---|---|---|
| ligne 1 `MODULE_UNIQUEID` | `GI` (**par position**), sous-systèmes C++ (par clé) | identifiant du module |
| ligne 2 `MODULE_NAME` | `GI` (par position), `LPModInfoSubsystem` | nom dans le gestionnaire, dans l'avertissement |
| ligne 3 `MODULE_AUTHOR` | `GI` (par position) | auteur dans le gestionnaire |
| `MODULE_DEFAULT_ENABLED` | `LPHairstyleSubsystem` | coché au premier démarrage où il est présent |
| `MODULE_GAME_VERSION` | `LPModInfoSubsystem` | avertissement de version |
| `MODULE_LINK`, `MODULE_DESCRIPTION`, `MODULE_REQUIREMENTS`, `MODULE_VERSION`, `MODULE_CREATED`, `MODULE_LICENSE`, `MODULE_AUTHOR_LINK`, `MODULE_PRICE`, `MODULE_PACKAGED` | aucun code | affichées telles quelles : le gestionnaire montre le fichier entier ; `GetModuleMetadata` les rend accessibles aux Blueprints |
`MODULE_REQUIREMENTS` n'est pas vérifié : aucun contrôle, aucune chaîne `MODULE_REQUIREMENTS` dans `GI` ; seuls
`MODULE_UNIQUEID:`, `MODULE_NAME:` et `MODULE_AUTHOR:` sont découpés (`LPStringCompat::Split`). Un mod dont un
prérequis manque se charge quand même. Les trois premières lignes sont lues par position : `BuildMod.ps1` en contrôle
l'ordre. Réserve : la recherche dans les Blueprints porte sur les noms de clés écrits en clair ; une clé assemblée à
l'exécution n'y apparaîtrait pas.
### 3.3 Notions de visibilité, verrouillage, priorité et dépendances
| Notion | Jeu (`.lpmod`, Blueprints) | Moteur (`.uplugin`) | État |
|---|---|---|---|
| Visible / masqué | aucune notion : tout `.lpmod` présent est listé ; le gestionnaire affiche le fichier entier dans le panneau de détail (`FullString`) | `Hidden` : lu, utilisé seulement par le navigateur de plugins de l'éditeur ; sans effet en jeu | pas de masquage possible |
| Verrouillé / non désactivable | `vin_Base` en dur dans `UI/ModuleTick` (case sans effet, boutons d'ordre masqués) ; aucune clé `.lpmod` | aucun verrouillage d'un plugin `Mod` : monté dès qu'il est présent | seul `vin_Base` est verrouillé |
| Module système chargé avant les autres (dossier distinct) | aucun : tous les modules sont dans `Content/Modules/<id>` | les plugins du jeu (`Plugins/`, cuits avec lui) sont configurés avant ceux de `Mods/` | existe côté moteur seulement, hors de portée d'un mod |
| Ordre de chargement / priorité | `GI.ModEnableds` ordonné, `GI.SortLoadOrder` ; le plus bas dans la liste affichée l'emporte, par nom de fichier (§ 2.4) ; fichiers texte seulement | seul ordre réglable : `LoadingPhase` des modules de code (un mod n'en a pas) ; conteneurs montés avec la même priorité (`FCoreDelegates::MountPak.Execute(PakPath, 0)`, `PluginManager.cpp`) | ordre réel pour les textes, aucun pour les assets |
| Dépendances | `MODULE_REQUIREMENTS` non vérifié | `Plugins` appliqué ; dépendance obligatoire absente : le jeu ne démarre pas ; `Optional`, `Sealed`, `DisallowedPlugins` (§ 3.1) | côté jeu : aucune ; côté moteur : bloquante |
Conséquence pour les mods : ne jamais déclarer de dépendance obligatoire entre mods dans le `.uplugin` (un mod
manquant empêcherait le jeu de démarrer). Un prérequis entre mods se gère côté jeu (§ 6, E2).
### 3.4 Doublons entre descripteurs
| Information | `.uplugin` | `.lpmod` | `pack.json` / config | Autres | Source de vérité | Écrit par |
|---|---|---|---|---|---|---|
| Identifiant | nom du fichier | `MODULE_UNIQUEID` | `chaine.mod` | dossiers | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` |
| Titre | `FriendlyName` | `MODULE_NAME` | `source` | 1re ligne de `CREDITS.txt` et de `README.txt` | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` ; README par `BuildMod.ps1` |
| Auteur | `CreatedBy` | `MODULE_AUTHOR` | `auteur`, enveloppé en « LifePlay Revival (animations by <auteur>) » | | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` |
| Description | `Description` | `MODULE_DESCRIPTION` | | | aucune désignée | `NewMod.ps1`, `DeliverAnims.ps1` |
| Version | `VersionName` | `MODULE_VERSION` | | README | `.lpmod` (recopié) | `BuildMod.ps1` : `MODULE_VERSION` → `VersionName` dans la copie livrée |
| Lien de l'auteur | `CreatedByURL` | `MODULE_AUTHOR_LINK` | `url_auteur` ou `url` | | `.lpmod` (recopié) | `BuildMod.ps1` : `MODULE_AUTHOR_LINK` → `CreatedByURL` |
| Licence | | `MODULE_LICENSE` | `licence`, `credit` | `CREDITS.txt`, description | aucune désignée | `DeliverAnims.ps1` |
| Version du jeu visée | | `MODULE_GAME_VERSION` | | README (×3), nom du zip | `BuildMod.ps1` | `BuildMod.ps1` (ajoute aussi `MODULE_PACKAGED`) |
`Tools/NewMod.ps1` et `Tools/anim/DeliverAnims.ps1` écrivent le `.uplugin` et le `.lpmod` source. `Tools/BuildMod.ps1`
retouche la copie livrée (clés ci-dessus, plus `MODULE_PACKAGED` et `MODULE_GAME_VERSION`) et écrit le README depuis
le `.lpmod`.
Cause des divergences : `DeliverAnims.ps1` crée les descripteurs une seule fois (`New-IfMissing`). Une modification
ultérieure de `pack.json` n'est jamais reportée. Divergences constatées :
- `lpr_anim_quaternius` : `MODULE_NAME` « Quaternius Animations » contre `pack.json:source` « Quaternius Universal
Animation Library (Standard) » ; `MODULE_LICENSE` « CC0 » contre `pack.json:licence` « CC0 1.0 ».
- `lpr_hair_pocolov` : auteur « LifePlay Revival (hairstyles by Pocolov) » (descripteurs) contre « Pocolov (Pocolov
Studio, artist Thang) » (`Tools/mods/lpr_hair_pocolov.json`, `CREDITS.txt`).
- `NewMod.ps1` n'écrit pas `MODULE_DEFAULT_ENABLED`.
Champs constants ou sans usage : `.uplugin` `FileVersion`, `Version` (entier), `Category`, `IsBetaVersion`,
`Installed` ; `.lpmod` `MODULE_REQUIREMENTS: vin_Base`, `MODULE_PRICE: Free`, `MODULE_LINK` (URL du dépôt),
identiques dans les trois mods.
## 4. Classes et fichiers
| Élément | Rôle |
|---|---|
| `FPluginManager` (moteur) | découverte de `Mods/`, activation, montage du contenu |
| Registre d'assets (moteur) | `AssetRegistry.bin` des plugins, recherche par chemin / classe |
| `Plugins/ModdingExtra` : `LoadTxt`, `SaveTxt`, `GetAllFilesFromFolder`, `LPDataCache` | accès disque aux `Modules` (fichiers nus, `DirectoriesToAlwaysStageAsNonUFS`) ; cache analysé des formats `CLÉ: valeur` dont `.lpmod` |
| `Content/BPs/GI.uasset` | `LoadSettings`, `ReadModLauncher`, `removeDeletedMods`, `PopulateRaws`, `IsModEnabled`, `CreateModManagerWidget`, `CloseModManager`, `ModEnableds` |
| `UI/ModManager`, `UI/MainMenu` | gestionnaire de mods ; entrée de l'écran titre (`LPTitleModManager`) |
| `ULPModInfoSubsystem` | lecture des `.lpmod`, `GetModuleMetadata`, `IsModuleEnabled`, avertissement de version |
| `ULPModSettingsLibrary::SaveModSelection` | enregistre `ModEnableds` seul dans `GameSettings.sav` |
| `ULPHairstyleSubsystem` | `MODULE_DEFAULT_ENABLED`, coiffures `.lphair` des modules cochés |
| `ULPModDailiesSubsystem`, `ULPModDailiesLibrary::IsModDaily` | clips de mod → `Room.Dailies` ; reconnaissance des clés de mod dans `Person::PlayDaily` |
| `ULPSessionLogSubsystem` | inventaire des modules au démarrage (journal) |
| Outils | `NewMod`, `EditMod`, `ModSource`, `BuildMod`, `SplitModules`, `HairPipeline`, `anim/AnimPipeline`, `anim/DeliverAnims` |
## 5. Capacités actuelles
Un mod peut, sans recompiler ni recuire le jeu :
- ajouter ou modifier des données de jeu en texte (actions, scènes, stats, quêtes, dialogues, IA, personnages, mondes) ;
- apporter des assets cuits : coiffures (H3), animations (AP5–AP7), tout asset que le jeu trouve par le registre ;
- être coché ou décoché dans le gestionnaire (sauf les animations, § 6 E1), coché d'office au premier démarrage ;
- être ordonné avec les autres modules dans le gestionnaire (le plus bas l'emporte, par nom de fichier, pour les
fichiers texte) ;
- prévenir le joueur s'il a été construit pour une autre version du jeu ;
- documenter licence, auteur, liens et crédits (affichés, non interprétés).
Il ne peut pas : apporter du code (C++ ou Blueprint exécuté en Shipping), remplacer un asset du jeu de base,
exiger un autre mod (`MODULE_REQUIREMENTS` n'est pas vérifié ; une dépendance `.uplugin` obligatoire bloquerait le
démarrage), ni être démonté à chaud (le plugin reste monté, décoché ou non).
## 6. Évolutions possibles
| Réf. | Évolution | Intérêt | Ampleur |
|---|---|---|---|
| E1 | `LPModDailies` : ne garder que les clips des modules cochés (même règle que `IsModuleEnabled`), au remplissage de `Room.Dailies` | un seul interrupteur pour tous les types de contenu ; clé « inconnue » quand le mod est décoché | petit lot C++, essai empaqueté coché / décoché |
| E2 | Vérifier `MODULE_REQUIREMENTS` au démarrage et à la fermeture du gestionnaire (avertissement comme pour la version) | prérequis manquant signalé au lieu d'un mod à moitié fonctionnel | petit lot C++ |
| E3 | Contrôle de cohérence des deux moitiés : `Mods/<id>` sans `Modules/<id>` (assets orphelins) ou l'inverse (`.lphair` / clés vers des assets absents) | désinstallation partielle détectée | petit lot C++ |
| E4 | Avertissement de version élargi aux mods d'assets décochés (un conteneur incompatible est monté même décoché) | prévenir avant un échec de chargement | très petit |
| E5 | Détection des collisions : deux mods qui déclarent la même clé d'animation, la même coiffure, le même identifiant d'action | comportement déterministe, message clair | moyen |
| E6 | Lecture des trois premières lignes du `.lpmod` par clé plutôt que par position (Blueprint `GI` ou relais C++) | un `.lpmod` écrit à la main dans le désordre ne casse plus le gestionnaire | moyen (graphe de `GI`) |
| E7 | Ne pas monter les plugins des mods décochés (liste lue avant le montage, `ExplicitlyLoaded` + montage par `IPluginManager::MountExplicitlyLoadedPlugin`) | mémoire et temps de démarrage ; mod réellement inerte | moyen, à prototyper (ordre de démarrage) |
| E8 | Déclaration explicite des animations (`.lpanim` : clé, clips F/M, catégorie, usage, posture) plutôt que par convention de nom | catalogue en jeu, filtres par posture, alias, plusieurs variantes par clé | moyen |
| E9 | Autres types d'assets par le même schéma registre + fichier texte : vêtements, meubles, décors de pièce (`.lpworld` vers `/<id>/…`) | extension naturelle de H3 | un lot par type |
| E10 | Empreintes dans l'archive (SHA-256 par fichier, vérifiées par un outil d'installation) | intégrité, mods modifiés détectés | petit (outillage) |
| E11 | Gestionnaire enrichi : description, licence, lien, version, état (cuit pour, prérequis, conflits), catalogue d'animations | information du joueur, aujourd'hui texte brut | moyen (UI) |
| E12 | Ordre étendu aux mods d'assets et clés d'animation (aujourd'hui seuls les fichiers texte suivent l'ordre) | surcharge contrôlée aussi pour les animations et les coiffures | moyen ; dépend de `PopulateRaws` et de `LPModDailies` |
| E13 | Notions déclarées dans le `.lpmod` : `MODULE_LOCKED`, `MODULE_HIDDEN`, `MODULE_PRIORITY` ou rang système, en remplacement du verrouillage en dur de `vin_Base` | verrouillage, masquage et priorité par module, sans Blueprint codé en dur | moyen (graphe de `UI/ModuleTick` et de `GI`) |
| E14 | Descripteur unique : `.lpmod` (ou `pack.json`) source de vérité, `.uplugin` et README générés à chaque construction, `New-IfMissing` remplacé par une régénération | fin des divergences entre descripteurs (§ 3.4) | petit à moyen (outillage) |
| E15 | Relevé `LPDumpFunction` : afficher le nom des événements personnalisés et des macros | confirmer le sens de la boucle de `PopulateRaws` (§ 2.4) | petit (outillage) |
Recommandation : E1 en premier (seule incohérence visible du joueur), puis E2 et E3 dans un même lot (mêmes
lectures que `LPModInfoSubsystem`), puis E14.
Un prototype des notions de la § 3.3 (prérequis, propriétés déclarées, ordre, état par mod) a été construit et validé dans la sandbox : voir la § 7.
## 7. Prototype dans la sandbox (lots SB0 à SB4)
La sandbox (addon `D:\Game\LifePlay-Sandbox`, plugin `LPSandbox`, doc [21](21_Sandbox_Addon.md)) a servi de banc d'essai
au concept « modules système, bibliothèques, mods » avant toute modification du jeu. Conception :
`D:\Game\LifePlay-Sandbox\Docs\SB0-conception.md` (décisions utilisateur du 2026-10-07). Le registre est en C++ et Slate,
sans le Blueprint `GI`. Preuves sous `Docs/Migration/etat/sandbox/` (dossiers `SB1`, `SB2`, `SB4`).
### 7.1 Ce qui est prouvé
| Élément | Résultat | Preuve |
|---|---|---|
| Registre C++ (`FLPSandboxModRegistry` : découverte, lecture, contrôles, états `Ready`, `Disabled`, `Missing`, `Refused`) | 7 modules sandbox découverts, tous `pret` au démarrage | `SB1/SB1-SPEC.md` (§ 2, 3) ; `SB1/SB1-VAL.md` E1 |
| `.lpmod` lu par clé, rétrocompatible | 16 `.lpmod` du jeu lus sans erreur ni avertissement (`Modules : 7 sandbox, 16 jeu`) | `SB1/SB1-VAL.md` E1 ; `SB2/SB2-VAL.md` E1 ; `SB4/SB4-VAL.md` F1 |
| Clés étendues `MODULE_SYSTEM`, `MODULE_HIDDEN`, `MODULE_LOCKED`, `MODULE_KIND`, `MODULE_DEFAULT_ENABLED` | portées par les descripteurs des 7 modules ; section « Système » et case « modules masqués » de la barre latérale | `SB1/SB1-SPEC.md` § 2, 8 ; `SB1/SB1-VAL.md` (contrôle visuel : aspect de la barre latérale, PASS) |
| Ordre système par index hexadécimal du dossier | `lps_system_core`, `lps_system_animmeasure`, `lps_lib_scene`, `lps_lib_actors`, puis les mods | `SB1/SB1-VAL.md` E1 ; `SB4/SB4-VAL.md` F1 |
| Ordre et cases des mods persistés | `Saved/sandbox_mods.ini` ; l'ordre du fichier prime sur l'ordre alphabétique ; mod décoché signalé `desactive` | `SB1/SB1-VAL.md` E4, E4b |
| Refus mod → mod | `lps_test_bad` refusé : « un mod ne peut pas requerir un autre mod » ; les 3 mods restent prêts | `SB1/SB1-VAL.md` E2 |
| Prérequis absent ou désactivé signalé sans bloquer | sans `lps_lib_scene` : mods en « prerequis manquant », scène neutre, aucun plantage ; l'ouverture mémorisée est refusée puis repli sur le premier mod prêt | `SB1/SB1-VAL.md` E3 ; `SB2/SB2-VAL.md` E3 |
| Cycle de prérequis refusé | règle spécifiée (tous les modules du cycle `Refused`) ; pas d'essai dédié dans les rapports de validation | `SB1/SB1-SPEC.md` § 3 (règle 3) |
| Activé ≠ ouvert | 3 mods activés et prêts, un seul ouvert à la fois ; enchaînement couple, interaction, mesure sans résidu | `SB1/SB1-VAL.md` E1 et contrôle visuel (b) ; `SB2/SB2-VAL.md` contrôle visuel (b) |
| Contexte filtré : un mod ne reçoit que les bibliothèques qu'il déclare | `FLPSandboxContext` rempli selon `MODULE_REQUIREMENTS` (`Scene` et `Actors` nuls si non requis) ; aucun accès de mod à mod | `SB2/SB2-CART.md` § 2.1 ; `SB2/SB2-VAL.md` E1 à E3 |
| État de chaque mode sorti dans son mod, sans régression | tournées Quaternius et FreeAnimationsPack : tous les `.json`, `.csv`, `.txt` identiques à la référence ; verdicts `avertissement` (8 avertissements identiques) et `ok` ; `LPAnimEval` identique | `SB2/SB2-VAL.md` E4 ; `SB4/SB4-VAL.md` F3 ; référence `SB2/SB2-REF.md` |
| Défaut O1 (pièce invisible après une Mesure) corrigé | `ShowRoom` symétrique de `HideRoom`, contrôle visuel utilisateur | `SB2/SB2-O1-BUILD.md` |
| Barre latérale (sections Mods, Système, Jeu ; cases, ▲ ▼, ▶, ⚠) | contrôle visuel utilisateur : pas de défaut | `SB1/SB1-VAL.md`, `SB2/SB2-VAL.md` |
| Déclencheur éphémère non mémorisé (SB4-T0) | `Open(Id, Context, bRemember)` ; `Saved/sandbox_mode.txt` n'écrit plus `Open` dans `sandbox_mods.ini` (journal `Ouvert : <id> (declencheur, non memorise)`) ; `Open=lps_couple` intact après deux tournées | `SB4/SB4-VAL.md` F2 ; build `SB4/SB4-BUILD.md` |
Les 20 images FreeAnimationsPack qui diffèrent d'une tournée à l'autre (rendu non déterministe, écarts de taille
inférieurs à 1,2 %) ont été jugées sans défaut par l'utilisateur (`SB1-VAL`, `SB2-VAL`).
### 7.2 Ce qui n'est pas prouvé, limites
| Limite | Détail |
|---|---|
| Séparation physique en plugins | abandonnée à la porte G-SB3 (décision utilisateur 2026-10-07) : les mods du jeu sont du contenu (`.lpmod` + pak), `LPAnimMeasure` est utilisé par le jeu en Shipping. Conséquence : sans `lps_lib_scene`, la pièce est quand même construite par le GameMode ; seul le registre refuse les mods (`SB2/SB2-VAL.md` E3, doc 21) |
| Persistance | propre à la sandbox (`Saved/sandbox_mods.ini`). La cohabitation avec `GI.ModEnableds` et `GameSettings.sav` n'est pas traitée |
| Mods du jeu | lecture des `.lpmod` seulement (état `DataOnly`) : pas d'activation du contenu du jeu, pas de lecture d'ordre depuis `ModEnableds` |
| Cycle de prérequis | non essayé dans les rapports de validation |
| Compilation | éditeur Development seulement ; le registre n'a pas été essayé en Shipping |
### 7.3 Transposition au jeu
| Élément prouvé | Évolution de la § 6 servie | Travail côté jeu |
|---|---|---|
| Contrôle des prérequis (états `Missing`, `Refused`, repli, sans bloquer) | E2 | lire `MODULE_REQUIREMENTS` au démarrage et à la fermeture du gestionnaire ; avertissement comme pour la version ; ne jamais passer par une dépendance `.uplugin` |
| Lecture par clé, 16 `.lpmod` du jeu sans erreur | E6 | remplacer le découpage par position de `GI` par une lecture par clé (relais C++ vers `LPDataCache`) |
| Propriétés déclarées (système, masqué, verrouillé, index de dossier) | E13 | remplacer le verrouillage en dur de `vin_Base` (`UI/ModuleTick`) par `MODULE_LOCKED` ; ajouter `MODULE_HIDDEN`, `MODULE_SYSTEM` et l'ordre système par index |
| Registre et état par mod | (nouveau) | studio de création de personnage comme module système ; Pocolov déclare `MODULE_REQUIREMENTS` vers le studio |
| Option legacy / modern | (nouveau, demande utilisateur) | d'abord un paramètre ; puis le legacy en mod ; puis retrait |
| Persistance | (nouveau) | `GI.ModEnableds` reste la source pour les mods du jeu ; le registre C++ s'y branche (à concevoir : lecture, écriture, ordre) |
| Déclencheur éphémère non mémorisé (SB4-T0) | principe | tout lancement automatisé (tournée, test, pilote) ne doit jamais modifier l'état persistant du joueur |