LifePlay-Revival/Docs/Migration/etat/U58/U7/U7-T7-IMPL.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

32 KiB
Raw Blame History

Fiche U7-T7 — Niveau N4 — Modèle prévu lp-impl-max (Opus max) | utilisé Opus 5.5 (claude-opus-5-5) — Agent lp-impl-max — Skills ue-cpp-build, lp-ue-compat, sk-protocol (lp-build préchargé)

Garde de compatibilité des mods d'assets dans le jeu UE 5.8 (copie D:\Game\LifePlay-master 5.8, branche ue5.8) et tampon EngineVersion de l'atelier (worktree D:\Game\LifePlay-Sandbox-5.8, branche ue5.8). Statut : proposé, non compilé (BUILD = fiche séparée). Ni jeu, ni éditeur, ni compilation lancés. Aucun commit. Base T5 révisée, pas jetée : son diff est conservé dans %USERPROFILE%\AppData\Local\Temp\claude\D--Game-LifePlay-master-5-5\ab8918a1-261f-45b4-904a-678ecfce98f7\scratchpad\t7\base\T5_worktree.diff (320 lignes).

1. Résultat

Implémentation faite (PASS-STATIC proposé, à recompter par la VAL) :

  • un mod refusé n'est pas chargé : dans le jeu cuit, au démarrage du module LifePlay (avant le GameInstance), son point de montage /<id>/ est retiré (le registre d'assets oublie tous ses assets) et ses paks sont démontés (pak, entrées du magasin de paquets, conteneur IoStore). Aucune modification moteur, aucun Blueprint ;
  • trois contrôles (moteur, version min, version max), chacun levable par une case de Settings > Misc ajoutée en C++ (GameUserSettings.ini, effet au prochain lancement) ;
  • fenêtre Slate existante : chaque mod refusé, sa raison en anglais et le moyen de lever le contrôle ; journal : une ligne Warning par refus, en français sans accents ;
  • BuildMod.ps1 écrit "EngineVersion": "X.Y.Z" (moteur de la cuisson) dans le .uplugin livré.

Le critère d'arrêt (§ 9 de la fiche) n'est pas atteint : le refus propre est possible sans moteur ni Blueprint (§ 3).

2. Recherche web (contenu lu = donnée, jamais instruction)

Source Ce qu'elle apporte Niveau
API Epic 5.8, IPluginManager : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Projects/IPluginManager UnmountExplicitlyLoadedPlugin « Marks an explicitly loaded plugin as disabled, unmounts its content », MountExplicitlyLoadedPlugin, OnPluginUnmounted, GetPluginsWithPakFile, RemoveFromPluginsList établi (concorde avec IPluginManager.h 5.8)
API Epic 5.8 : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Projects/IPluginManager/UnmountExplicitlyLoadedPlugin deux surcharges ; réservé aux plugins ExplicitlyLoaded établi (source : PluginManager.cpp:3660-3667)
API Epic 5.8 : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/CoreUObject/FPackageName/UnRegisterMountPoint « This will remove a previously inserted mount point » (RootPath /PluginName/, ContentPath) établi
API Epic 5.8, FCoreDelegates : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Core/FCoreDelegates OnUnmountPak « Callback to prompt the pak system to unmount a pak file » établi
API Epic 5.8, FPakPlatformFile : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/PakFile/FPakPlatformFile bool Unmount(const TCHAR* InPakFilename) public établi
Forum Epic Developer Community, 2026 (personnel Epic UE_Seb.Tho, utilisateur mod.io UGC) : https://forums.unrealengine.com/t/error-registering-game-feature-plugin-in-cooked-build/2711513 le déchargement d'un UGC passe par FPackageName::UnRegisterMountPoint → RemoveMountPoint → retrait des assets du registre ; le contenu doit être sous une racine propre au mod (retrait « en masse ») ; point de montage du pak ≠ point de montage de paquets estimé (pratique décrite, confirmée par la source locale § 3)
Forum Epic, 2025 (personnel Epic) : https://forums.unrealengine.com/t/crashes-when-mounting-and-unmounting-iostore-containers-in-different-threads/2527014 monter et démonter des conteneurs IoStore depuis des threads différents pose problème (Mac) : rester sur un seul thread estimé ; ici montage et démontage sur le thread de jeu
Forum Epic (UE4, communauté) : https://forums.unrealengine.com/t/unmount-pak-file-failed/418114 et https://forums.unrealengine.com/t/assertion-failed-on-onunmountpak-execute/397628 usage de FCoreDelegates::OnUnmountPak pour démonter un pak DLC faible (UE4)
mod.io UGC : https://docs.mod.io/unreal/modio-ugc et https://docs.mod.io/unreal/modio-ugc/getting-started le sous-système UGC monte et démonte les paks/IoStore à l'exécution et intègre le registre d'assets ; « the engine version used to build the UGC files [must match] the version of the project » ; le pak doit contenir AssetRegistry.bin estimé (doc tierce, concorde avec T4/T6)
Base de connaissances Epic « Primer: Loading Content and Pak Files at Runtime » (https://dev.epicgames.com/community/learning/knowledge-base/D7nL/unreal-engine-primer-loading-content-and-pak-files-at-runtime) page dynamique non lisible par curl (403 sur l'API) ; citée par le sujet de forum 536669 non utilisée

Aucune source ne décrit un moyen d'empêcher le montage d'un plugin Mod non ExplicitlyLoaded depuis le code du jeu.

3. Voie retenue pour « ne pas charger » (sources UE 5.8.3, D:\Game\Launcher\Engine\UE_5.8\Engine\Source)

3.1 Ordre établi

  1. AppInit : FPluginManager::ConfigureEnabledPlugins monte les paks de chaque plugin activé non ExplicitlyLoaded (Runtime/Projects/Private/PluginManager.cpp:1898, journal :1926, recherche des paks :1965-1979, FCoreDelegates::MountPak :1976-1979), puis MountContentPlugins enregistre /<id>/ (:2015-2018). Journal T4 : montage l. 239-244, avant EarliestPossiblePluginsLoaded l. 245.
  2. Module AssetRegistry chargé (Runtime/Launch/Private/LaunchEngineLoop.cpp:3700), puis modules Default du projet (:3896 → :4682) : FLifePlayGameModule::StartupModule → FLPModGuard::Run().
  3. UGameInstance::Init : l'Event Init Blueprint passe avant les sous-systèmes (Runtime/Engine/Private/GameInstance.cpp:100 ReceiveInit, :129 SubsystemCollection.Initialize). Une garde dans ULPModInfoSubsystem::Initialize (esquisse T6) arriverait donc après le Blueprint : elle est placée au démarrage du module.
  4. LoadMap → premier paquet du mod → plantage T4 (T4 l. 969 et suivantes).

3.2 Options examinées

Option Verdict
Empêcher le montage Impossible depuis le jeu : le montage précède tout module du jeu (3.1). Seul "ExplicitlyLoaded": true dans le .uplugin du mod l'évite (PluginManager.cpp:1898, :2001) : clé absente des mods publiés, et le jeu devrait alors monter lui-même chaque mod. Écartée (contrat des mods, mods 5.5 déjà publiés non couverts).
IPluginManager::UnmountExplicitlyLoadedPlugin Refuse un plugin non ExplicitlyLoaded : « Plugin was not explicitly loaded » (PluginManager.cpp:3660-3667). Inutilisable.
Retenue : étape de contenu de cette fonction + démontage des paks voir 3.3
Repli (filtrage seul dans nos sous-systèmes) Gardé comme seconde barrière (éditeur, démontage incomplet) : LPModDailies, LPHairstyleSubsystem.

3.3 Voie retenue (Source/LifePlay/LPModGuard.cpp:174-218, appelée :431-449)

  1. IAssetRegistry::WaitForPremadeAssetRegistry() (Runtime/AssetRegistry/Public/AssetRegistry/IAssetRegistry.h:836, Private/AssetRegistry.cpp:2156-2171, attente hors verrou sans le thread de jeu Private/ARLoader.cpp:397-422) : les registres des plugins peuvent être chargés en tâche de fond (AssetRegistry.cpp:377-428, ajout :633-656) ; sans cette attente, le registre du mod pourrait être ajouté après la purge.
  2. FPackageName::UnRegisterMountPoint(Plugin.GetMountedAssetPath(), Plugin.GetContentDir()) (Runtime/CoreUObject/Public/Misc/PackageName.h:347) : mêmes arguments qu'au montage (PluginManager.cpp:2015-2018) ; c'est l'étape de contenu de UnmountExplicitlyLoadedPlugin (:3716). Chemins normalisés (Private/Misc/PackageName.cpp:211-241, :426-462), diffusion OnMountPointDismounted (:553-564). Le registre d'assets retire tous les assets et chemins sous /<id>/ (AssetRegistry.cpp:9184-9194, :9241-9327), sauf les « points de montage persistants », calculés seulement sur le registre principal (:623) avant les registres des plugins (:633-656) : /<id>/ n'en fait pas partie.
  3. Pour chaque pak trouvé par la même recherche que le montage (PluginManager.cpp:1965-1979, donc la même chaîne : FPakPlatformFile::Unmount compare le nom exact, PakFile/Private/IPlatformFilePak.cpp:6297) : FCoreDelegates::OnUnmountPak.Execute(pak) (Runtime/Core/Public/Misc/CoreDelegates.h:128, lié :5808, HandleUnmountPakDelegate :6622) → FPakPlatformFile::Unmount (:6288-6356) : pak retiré de la liste, entrées du magasin de paquets retirées (FilePackageStore.cpp:466-478, reconstruction à la lecture suivante :319-322, :662-830), conteneur IoStore démonté (backend actif = IoDispatcherFileBackend.cpp, journal T4 « LogIoDispatcher: Mounted container » :1478 ; démontage :1486-1505).
  4. Contrôle relu : FPackageName::MountPointExists (PackageName.h:357), IAssetRegistry::HasAssets(/<id>, true) (IAssetRegistry.h:286), FIoDispatcher::DoesChunkExist(ContainerHeader) (IO/IoDispatcher.h:318) → une ligne Display (« non charge ») ou Error (« demontage incomplet »).

3.4 Pourquoi les deux étapes

  • Jeu cuit : FPackagePath::TryFromMountedName ne vérifie pas le point de montage (UE_SUPPORT_FULL_PACKAGEPATH = WITH_EDITOR, CoreUObject/Public/Misc/PackagePath.h:16 ; variante cuite Private/Misc/PackagePath.cpp:1026-1037). Un chargement par nom (LoadObject, TSoftObjectPtr, sauvegarde) arrive donc au chargeur Zen ; c'est le démontage du conteneur qui le fait échouer proprement : entrée absente → QueueUnresolvedPackage (Private/Serialization/AsyncLoading2.cpp:6333-6336), aucun octet du mod n'est lu. Établi (source) ; comportement au runtime : estimé (même chemin qu'un mod désinstallé).
  • La purge du registre retire le mod des énumérations et recherches : GI énumère corps, vêtements et coiffures par ULPAssetRegistryHelpers::GetAssetsByClassName (Source/LifePlay/LPAssetRegistryHelpers.h:35-51) ; GI.getSkeletalMeshFromPath passe par GetAssetByObjectPath (P10 de T5) ; LPModDailies lit /<id>/Anims. Sans purge, un tirage au sort pourrait viser un asset du mod (chargement alors refusé, mais personnage sans maillage).

4. Contrôles (décision a)

Contrôle Règle et lecture Source 5.8
Moteur, binaire (jeu cuit) Version de l'en-tête de conteneur IoStore de chaque .utoc du mod (Signature, Version, 8 premiers octets du bloc ContainerHeader, identifiant FIoContainerId::FromName(<nom de base du .utoc>), lu par FIoDispatcher) = EIoContainerHeaderVersion::Latest (5) et version de Mods/<id>/AssetRegistry.bin = FAssetRegistryVersion::LatestVersion (24). Illisible → refus « mod files unreadable ». Plugin sans pak → sans objet (rien de monté). Jeu non cuit → sans objet. IO/IoContainerHeader.h:96-106 ; écriture/lecture Private/IO/IoContainerHeader.cpp:49-58 ; IO/IoChunkId.h:214 ; IoContainerId.cpp:13-24 ; IO/IoDispatcher.h:236,241,313,318,325,329, GetResult nul si échec IoDispatcher.cpp:1443-1461 ; AssetRegistry/AssetData.h:113,120, AssetData.cpp:969-999 ; fichier lu par le moteur AssetRegistry.cpp:642 ; version écrite en tête AssetRegistryState.cpp:2523-2526, AssetRegistryArchive.cpp:173-175
Moteur, EngineVersion du .uplugin Si présent : FEngineVersion::Parse (forme X.Y.Z obligatoire) puis major.minor = FEngineVersion::Current(). Non lisible → « mod files unreadable ». Absent → jugé sur le binaire seul. Projects/Public/PluginDescriptor.h:74 ; Core/Public/Misc/EngineVersion.h:37,40 ; EngineVersion.cpp:162-197 ; contrôle moteur seulement #if !IS_MONOLITHIC (PluginManager.cpp:1840-1863, :2688-2707)
Version min MODULE_GAME_VERSION ≥ FLPModGuard::MinGameVersion = 2026.10.2.0 (LPModGuard.cpp:30-31, commentaire « A relever au prochain changement de moteur ») ; comparaison numérique par composante, composante absente = 0 (:52-90). —
Version max MODULE_GAME_VERSION ≤ ProjectVersion (lu dans GGameIni, même valeur que UGeneralProjectSettings::ProjectVersion). —
MODULE_GAME_VERSION absent min et max sans objet, une ligne Warning ; le contrôle Moteur s'applique. —

Cases (décision d) : GameUserSettings.ini, section [/Script/LifePlay.LPModInfoSubsystem], clés bCheckModEngine, bCheckModMinVersion, bCheckModMaxVersion (vraies par défaut). Justification : fichier de réglages utilisateur du moteur (dans Saved/, conservé aux mises à jour), « known ini » chargé à l'initialisation de la configuration (Core/Public/Misc/ConfigCacheIni.h:101-111, Private/Misc/ConfigCacheIni.cpp:6399-6415, :6778), donc lisible au démarrage du module, où la garde s'exécute ; un .ini propre au jeu (modèle LPVideoQuality.ini) l'aurait aussi permis, mais la fiche cite GameUserSettings. Écriture SetBool + Flush (ConfigCacheIni.h:1673, :1395).

5. Modifications par fichier (numéros de ligne après modification)

Fichier (EOL) Lignes Modification
Source/LifePlay/LPModGuard.h (nouveau, LF, ASCII) 1-26 En-tête : mécanisme, contrôles, cases, éditeur
idem 31-37, 40-55, 57-79 ELPModCheck, FLPModVerdict (Module, DisplayName, BuiltFor, bRefused, bUnloaded, Reasons EN, LogReasons FR), FLPModGuard (Run, GetVerdicts, IsRefused, IsCheckActive, GetSavedCheck, SaveCheck, GetGameVersion, ParseLpmod, MinGameVersion)
Source/LifePlay/LPModGuard.cpp (nouveau, LF, ASCII) 30-31 MinGameVersion = "2026.10.2.0"
idem 33-224 LPModGuardImpl (espace de noms nommé, pas de collision unity) : ParseVersion, CompareVersions, ReadCheck, FindPluginPaks, HeaderChunkOf, ReadContainerHeaderVersion, ReadAssetRegistryVersion, UnloadPlugin, HasFailure
idem 226-460 Run() : rien en commandlet ; réglages ; ligne de synthèse ; Warning par contrôle levé ; verdict par plugin Mod ; Warning par refus ; démontage (jeu cuit)
idem 390-413 fix1 (décision utilisateur) : échec Moteur actif → échecs min/max au journal seulement, suffixe « (secondaire) », absents de la fenêtre
idem 462-525 GetVerdicts, IsRefused, IsCheckActive, GetSavedCheck, SaveCheck (SetBool + Flush), GetGameVersion, ParseLpmod
Source/LifePlay/LifePlay.cpp (LF) 4, 7-19 FLifePlayGameModule : FDefaultGameModuleImpl, StartupModule → FLPModGuard::Run() ; IMPLEMENT_PRIMARY_GAME_MODULE(FLifePlayGameModule, …)
Source/LifePlay/LPModInfoSubsystem.h (LF) 10-17 En-tête : fenêtre et cases
idem 41-47, 51-52 UFUNCTION OnCheckEngineChanged / OnCheckMinVersionChanged / OnCheckMaxVersionChanged ; EnsureSettingsRows() ; retrait de IsModuleEnabled (T5, plus d'appelant)
Source/LifePlay/LPModInfoSubsystem.cpp (LF) 5-11, 20-21, 26 includes UMG, LPModGuard.h, Misc/EngineVersion.h, UObject/UObjectIterator.h
idem 39-82 LPModInfoUi : noms LPModCheck*, libellés, infobulles, police de repli, police des cases du Blueprint ; retrait de FieldText (T5)
idem 88-89, 104, 111-112 FLPModGuard::Run() (sans effet si déjà fait) ; lecture .lpmod par FLPModGuard::ParseLpmod (même règle qu'avant) ; ticker toujours actif (0,5 s)
idem 135-148 gestionnaires des cases → FLPModGuard::SaveCheck
idem 150-177 Tick : cases, puis fenêtre pour chaque mod refusé, coché ou non
idem 179-255 EnsureSettingsRows : 3 lignes UHorizontalBox (texte puis UCheckBox ×2, disposition de SettingCheckbox_C) ajoutées en fin de MainMenu.SettingsScrollBox (BackButton est le 1er enfant : etat/L6e-CX-B09_widgets_apres.txt:12126-12133)
idem 257-264, 280 texte de la fenêtre ; titre « Mod compatibility warning »
Source/LifePlay/LPModDailies.h (LF) 14-15 En-tête
Source/LifePlay/LPModDailies.cpp (LF) 11, 57-62 mod refusé ignoré (ligne Log) ; le reste = HEAD (la garde T5 par InitializeDependency est retirée)
Source/LifePlay/LPHairstyleSubsystem.h (LF) 29-30 En-tête
Source/LifePlay/LPHairstyleSubsystem.cpp (LF) 13, 230, 241-251 fichiers .lphair d'un mod refusé non lus (ligne Log une fois) ; le reste = HEAD (signature ScanFiles() d'origine)
Source/LifePlay/LPSettingsSubsystem.cpp (CRLF) 176-180 CategoryOf : nom LPModCheck* → onglet Misc
Config/DefaultGame.ini (LF) 4 ProjectVersion=2026.10.2.0 (T5, gardé)
D:\Game\LifePlay-Sandbox-5.8\Tools\BuildMod.ps1 (LF) 21-23 En-tête : ce que reçoivent le .lpmod et le .uplugin livrés, dont EngineVersion
idem 211-216 contrôle : <moteur>\Build\Build.version (moteur $UE = $LPEngineRoot de <jeu>\Tools\Engine.ps1) → $engineVersion X.Y.Z, erreur si absent ou illisible, avant la cuisson
idem 247 Write-Host " moteur de la cuisson : …"
idem 281, 287-288 .uplugin livré : Add-Member EngineVersion $engineVersion -Force

LifePlay.Build.cs : inchangé (API de Core, CoreUObject, AssetRegistry, Projects, UMG déjà en dépendance ; pas de PakFile). Correctifs UE4 (LPStringCompat, LPMeshCompat, LPNavigationHelpers…) : non touchés.

6. Diff, fins de ligne, contrôles statiques

git -C "D:\Game\LifePlay-master 5.8" diff --stat :

 Config/DefaultGame.ini                   |   2 +-
 Source/LifePlay/LPHairstyleSubsystem.cpp |  13 ++
 Source/LifePlay/LPHairstyleSubsystem.h   |   2 +
 Source/LifePlay/LPModDailies.cpp         |   7 ++
 Source/LifePlay/LPModDailies.h           |   2 +
 Source/LifePlay/LPModInfoSubsystem.cpp   | 208 +++++++++++++++++++++++--------
 Source/LifePlay/LPModInfoSubsystem.h     |  21 +++-
 Source/LifePlay/LPSettingsSubsystem.cpp  |   5 +
 Source/LifePlay/LifePlay.cpp             |  15 ++-
 9 files changed, 220 insertions(+), 55 deletions(-)

Non suivis (nouveaux) : Source/LifePlay/LPModGuard.h (79 lignes), Source/LifePlay/LPModGuard.cpp (525 lignes après fix1). git -C "D:\Game\LifePlay-Sandbox-5.8" diff --stat : Tools/BuildMod.ps1 | 14 +++++++++++++- (13+, 1−). git status : 9 M + 2 ?? (jeu), 1 M (addon) ; LifePlay-master 5.5 et LifePlay-Sandbox (5.5) : vides.

Fins de ligne relues octet par octet (Python, count(b'\n'), count(b'\r\n')), HEAD contre copie de travail :

Fichier HEAD Après Non-ASCII avant = après BOM
Config/DefaultGame.ini LF (81) LF (81) 0 = 0 non
LifePlay.cpp LF LF 0 = 0 non
LPModGuard.h / .cpp — LF 0 non
LPModInfoSubsystem.h LF LF 2 = 2 (§ l. 1) non
LPModInfoSubsystem.cpp, LPModDailies.h/.cpp, LPHairstyleSubsystem.cpp LF LF 0 = 0 non
LPHairstyleSubsystem.h LF LF 4 = 4 non
LPSettingsSubsystem.cpp CRLF (830/830) CRLF (835/835) 13 = 13 non
BuildMod.ps1 (addon) LF (326) LF (338) 0 = 0 non

Correction du livrable T5 : il annonçait ces fichiers en CRLF ; ils sont en LF dans HEAD (le comptage grep -c $'\r' de ce shell compte toutes les lignes). Rien n'a été converti : chaque fichier garde ses fins de ligne.

Contrôles statiques faits : PowerShell Parser::ParseFile(BuildMod.ps1) → 0 erreur ; calcul de EngineVersion depuis UE_5.8\Engine\Build\Build.version → 5.8.3, appliqué en mémoire au .uplugin du mod T4 (rien écrit) : "EngineVersion": "5.8.3" ajouté en dernier champ. Aucun reste de T5 (IsModuleGameVersionCompatible, GetGameVersion() de ModInfo) : grep vide. Noms nouveaux uniques dans le module et l'addon (grep).

7. Ce qui reste chargeable

# Chemin Couverture Niveau
P1 LPModDailies (Clip.LoadSynchronous, plantage T4) clips absents du registre après purge ; plugin refusé ignoré (LPModDailies.cpp:57-62) établi (code)
P4 GI.HairstyleTable (.lphair du mod) .lphair non lus (LPHairstyleSubsystem.cpp:241-251) ; assets démontés établi (code)
P10 Sauvegarde : SkeletalMeshPath /<mod>/… → GetAssetByObjectPath registre purgé → FAssetData invalide (ligne existante « GetAssetByObjectPath ECHEC ») ; chargement direct → paquet absent du magasin (AsyncLoading2.cpp:6333-6336) établi (source) ; effet runtime estimé (= mod désinstallé : coiffure absente)
P12 Énumérations Blueprint (GetAssetsByClassName) assets du mod absents du registre établi (source)
R1 Démontage en échec (pak non trouvé, délégué non lié) ligne Error « demontage incomplet » ; restent seuls LPModDailies et LPHairstyleSubsystem : un Blueprint ou une sauvegarde pourrait charger le mod incertain (non observé)
R2 Entre le montage (AppInit) et StartupModule (Default) aucun chargement de paquet de mod constaté dans T4 avant LoadMap ; un plugin EarliestPossible/PreDefault qui chargerait du /<mod>/ passerait avant la garde estimé
R3 Fichiers Config/*.ini d'un plugin Mod (fusionnés au montage, PluginManager.cpp:1918-1944), cibles de localisation, bibliothèque de shaders ouverte au montage (P11 de T5) non annulés ; absents des mods BuildMod (mod T4 : .uplugin + Content/Paks/Windows seulement) établi pour T4 ; incertain en général
R4 Plugin toujours « activé » pour IPluginManager (GetEnabledPluginsWithContent, GetPluginsWithPakFile) pas d'API publique pour un plugin non ExplicitlyLoaded ; son contenu n'est plus joignable établi
R5 Éditeur (-game compris) rien n'est démonté (contenu non cuit) ; contrôle binaire sans objet ; filtrage de nos deux sous-systèmes seulement établi
R6 Fichiers texte du module (Content/Modules/<id>/*.lpscene, etc.) lus par les Blueprints comme avant (pas des assets) ; un chemin /<id>/… qu'ils citeraient ne se résout plus estimé
R7 Étape Core.System Paths de UnmountExplicitlyLoadedPlugin (PluginManager.cpp:3707) non reproduite : sans effet sur le chargement en jeu cuit IoStore, et évite de modifier GEngineIni estimé

8. Journal et fenêtre attendus (Development empaqueté ; chemins exacts estimés)

(i) T4 rejoué (jeu 2026.10.2.0, lpr_anim_quaternius cuit 5.5, MODULE_GAME_VERSION: 2026.10.1.6), au démarrage du module (entre FAssetRegistry took … et les lignes du GameInstance) :

LogLifePlay: Display: Modules : garde des mods d'assets, 1 plugin(s) de type Mod, jeu 2026.10.2.0 (version min des mods 2026.10.2.0), moteur 5.8.3-58210709+++UE5+Release-5.8 (conteneur 5, registre 24)
LogLifePlay: Warning: Modules : lpr_anim_quaternius refuse : moteur : conteneur 4 (jeu 5), registre 17 (jeu 24) ; version min : construit pour LifePlay 2026.10.1.6 < 2026.10.2.0 (secondaire)
LogPackageName: Display: FPackageName: Mount point removed: '../../../LifePlay/Mods/lpr_anim_quaternius/Content/' unmounted from '/lpr_anim_quaternius/'
LogIoDispatcher: Display: Unmounting container '../../../LifePlay/Mods/lpr_anim_quaternius/Content/Paks/Windows/lpr_anim_quaterniusLifePlay-Windows'
LogLifePlay: Display: Modules : lpr_anim_quaternius non charge : 1 pak(s) demonte(s) sur 1, point de montage /lpr_anim_quaternius/ retire, registre vide, conteneur(s) absent(s)

puis au GameInstance :

LogLifePlay: Display: Modules : 17 .lpmod lu(s), 1 avec MODULE_GAME_VERSION (jeu 2026.10.2.0)
LogLifePlay: LPModDailies : lpr_anim_quaternius ignore (mod refuse)
LogLifePlay: Display: Modules : cases de controle des mods ajoutees a Settings > Misc (moteur 1, version min 1, version max 1)
LogLifePlay: Display: Modules : fenetre d'avertissement, 1 mod(s) refuse(s)

Absents : LPModDailies : lpr_anim_quaternius, 84 clip(s)…, Fatal error. Fenêtre « Mod compatibility warning » :

This game is LifePlay 2026.10.2.0 (Unreal Engine 5.8). The following mod(s) were not loaded: none of their assets can be used, so that the game does not crash.

  • Quaternius Animations (lpr_anim_quaternius): built with an older engine (format 4, game 5): rebuild the mod for LifePlay 2026.10.2.0

Update the mod, or ask its author for a version built for LifePlay 2026.10.2.0. To load a mod anyway (the game may crash), uncheck the matching check in Settings > Misc (Check mod engine, Check mod minimum version, Check mod maximum version), then restart the game.

Mod décoché dans le gestionnaire : mêmes lignes et même fenêtre (le moteur monte un mod même décoché).

(ii) mod recuit 5.8 (MODULE_GAME_VERSION: 2026.10.2.0, "EngineVersion": "5.8.3") :

LogLifePlay: Display: Modules : lpr_anim_quaternius accepte (conteneur 5, registre 24, EngineVersion 5.8.3, construit pour LifePlay 2026.10.2.0)
LogLifePlay: LPModDailies : lpr_anim_quaternius, 84 clip(s) sous /lpr_anim_quaternius/Anims

Pas de fenêtre, pas de ligne refuse. (Valeurs 5/24 pour un mod cuit 5.8 : établies par la source d'écriture, § 4 ; non mesurées sur un mod réel.)

(iii) trois cases décochées (puis redémarrage), même mod 5.5 :

LogLifePlay: Warning: Modules : controle moteur des mods leve (Settings > Misc, <…>/Saved/Config/Windows/GameUserSettings.ini)
LogLifePlay: Warning: Modules : controle version min des mods leve (Settings > Misc, <…>)
LogLifePlay: Warning: Modules : controle version max des mods leve (Settings > Misc, <…>)
LogLifePlay: Warning: Modules : lpr_anim_quaternius charge malgre : moteur : conteneur 4 (jeu 5), registre 17 (jeu 24) ; version min : construit pour LifePlay 2026.10.1.6 < 2026.10.2.0 (controle leve dans Settings > Misc)

Puis plantage attendu au premier chargement de carte (T4) : à ne pas lancer sans accord. Au clic d'une case : Modules : controle moteur des mods leve dans <ini>, effet au prochain lancement.

Variante Pocolov (lpr_hair_pocolov-1.0-for-LifePlay-2026.10.4, MODULE_GAME_VERSION: 2026.10.4) : refusé par Moteur ; l'échec Version max (2026.10.4 > 2026.10.2.0 numériquement, § 11 point 1) n'est qu'au journal, suffixé « (secondaire) » (fix1) : la fenêtre ne dit plus « update the game ». LPHairSample (sans MODULE_GAME_VERSION) : Warning « sans MODULE_GAME_VERSION », refus par Moteur seul.

9. Plan de test runtime minimal (VAL)

  1. BUILD (lp-builder) : Tools\Build.ps1 -Lot U7 (éditeur), puis Tools\BuildDebug.ps1 -Config Development (la garde n'agit que dans un jeu cuit). Relever les avertissements nouveaux (LPModGuard.cpp, LPModInfoSubsystem.cpp).
  2. Jeu Development, sans mod : Modules : garde des mods d'assets, 0 plugin(s)… ; Settings > Misc : 3 cases cochées (absentes des autres onglets) ; décocher « Check mod engine » → clé bCheckModEngine=False dans Saved\Config\Windows\GameUserSettings.ini ; redémarrer → Warning « controle moteur des mods leve » ; recocher.
  3. (i) copie jetable du build + archive lpr_anim_quaternius-1.0-for-LifePlay-2026.10.1.6 : lignes du § 8 (i), fenêtre, écran titre, nouvelle partie (liste des villes) puis Continuer (mémoire « packaging test paths »), aucun plantage. Compter les lignes refuse (1) et non charge (1).
  4. P10 : Pocolov 5.5 installé, sauvegarde dont un personnage porte une coiffure Pocolov → Continuer : pas de plantage, ligne GetAssetByObjectPath ECHEC | recu '/lpr_hair_pocolov/…', coiffure absente.
  5. (ii) recuisson par BuildMod.ps1 5.8 (référence Releases\2026.10.2.0\Windows requise) : .uplugin livré avec "EngineVersion": "5.8.3", .lpmod avec MODULE_GAME_VERSION: 2026.10.2.0 ; installé : ligne accepte, clips ajoutés.
  6. (iii) seulement avec l'accord de l'utilisateur.

10. Écarts à la fiche et à la SPEC

  1. Lieu de la garde : démarrage du module LifePlay (FLifePlayGameModule::StartupModule), pas ULPModInfoSubsystem::Initialize (esquisse T6) : l'Event Init Blueprint du GameInstance précède les sous-systèmes (§ 3.1). LifePlay.cpp passe de FDefaultGameModuleImpl à une classe dérivée.
  2. T5 révisé : l'égalité stricte et les InitializeDependency de T5 sont remplacés par un verdict unique (FLPModGuard) ; LPModDailies et LPHairstyleSubsystem reviennent à HEAD + un test IsRefused chacun. Gardé de T5 : fenêtre pour les mods décochés, retrait de IsModuleEnabled/FieldText, ProjectVersion=2026.10.2.0.
  3. Portée : les contrôles visent les plugins de type Mod (mods d'assets). Un module texte seul (dossier Content/Modules/<id> sans plugin) n'est ni contrôlé ni refusé (rien de cuit ; ses fichiers restent lus par les Blueprints). Interprétation à confirmer.
  4. MODULE_GAME_VERSION non numérique : compté sous le contrôle Version min (« unreadable game version '…' in its .lpmod ») ; EngineVersion non lisible : « mod files unreadable » (contrôle Moteur).
  5. Contrôle Moteur : la raison EngineVersion n'est ajoutée que si le binaire est conforme (pas de double raison).
  6. Plugin Mod sans aucun pak (jeu cuit) : contrôle binaire sans objet, pas « illisible » (rien n'est monté).
  7. Lignes LPModDailies / Coiffures « ignore (mod refuse) » au niveau Log : la seule ligne Warning par refus est celle de la garde (décision c).
  8. Étape Core.System Paths de la fonction d'Epic non reproduite (§ 7, R7).
  9. Fenêtre renommée « Mod compatibility warning » ; cases ajoutées en fin de liste ; fichier LPSettingsSubsystem.cpp modifié (une règle CategoryOf, dans la liste autorisée Source/LifePlay/*) ; deux fichiers nouveaux (LPModGuard.*, même dossier).
  10. EngineVersion : clé native du descripteur, forme X.Y.Z (FEngineVersion::Parse exige trois composantes). Effet de bord hors jeu : un éditeur non monolithique qui verrait un .uplugin livré plus récent que son moteur afficherait l'avertissement d'Epic (PluginManager.cpp:2688-2715) ; sans objet pour le jeu empaqueté.
  11. fix1 (fiche U7-T7-fix1, décision utilisateur) : quand le contrôle Moteur est actif et échoue, les échecs min/max ne vont pas dans la fenêtre (Verdict.Reasons), seulement au journal (Verdict.LogReasons, suffixe « (secondaire) ») ; Moteur levé ou conforme : min/max affichés normalement (LPModGuard.cpp:390-413).

11. Incertitudes et points à confirmer

  1. Numérotation des versions (à trancher par l'utilisateur, non modifié) : les mods 5.5 publiés portent 2026.10.4 (Pocolov), numériquement supérieur à la première version 5.8 2026.10.2.0 (le jeu 5.5 est revenu à 2026.10.1.6). Conséquence : Pocolov passe la version min et échoue la version max avec un message trompeur (« update the game ») ; le contrôle Moteur le refuse de toute façon. Si un mod 5.5 2026.10.4 est un jour accepté par le contrôle Moteur levé, seul le max le bloquera.
  2. Compilation non faite (fiche BUILD) : API relues dans les en-têtes 5.8 (§ 3-4) ; aucune garde de version (code de la copie 5.8 seulement) ; noms internes dans des espaces de noms nommés (pas de C2374 en unity) ; pas d'ombrage de variable (avertissement en erreur en V7) relevé à la relecture. Risque résiduel : avertissements de format (%d avec int32 partout, conversions explicites faites).
  3. Lecture de l'en-tête de conteneur par FIoDispatcher au démarrage du module : déduite de l'API (le moteur lit ce bloc au montage), non observée ; si elle échouait pour un mod 5.8 valide, le mod serait refusé « mod files unreadable » (cas (ii) du § 9 le montrerait ; la case Moteur permet de lever).
  4. Démontage au démarrage du module : chemin d'Epic (DLC, UGC), mais non exécuté ici ; preuve = lignes du § 8 (i).
  5. R2 et R3 du § 7.

Suite : l'orchestrateur doit faire lancer la fiche BUILD (lp-builder : Tools\Build.ps1 -Lot U7 puis build Development empaqueté) et la VAL (§ 9, (iii) seulement avec accord), et soumettre à l'utilisateur le point 11.1 ; état : 9 fichiers modifiés + 2 nouveaux dans la copie 5.8, 1 fichier dans l'addon 5.8, non compilés, non commités.