| 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) |
| É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 |
| 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` |
| 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` |
| `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 |
| 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` |
| `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) |
| 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) |
| `.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 |