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

40 KiB
Raw Permalink Blame History

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 (données texte, GI), 15 (modules livrés à part), 16 (mods d'assets H3), 17 (gestionnaire de mods depuis l'écran titre), 20 (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

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

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)

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

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

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

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

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