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

21 KiB
Raw Blame History

Fiche U7-T11 — Niveau N3 — Modèle prévu lp-cart (Opus xhigh) | utilisé Opus 5.5 (claude-opus-5-5) — Agent lp-cart — Skills sk-protocol, lp-commandlets, lp-paths, ue-cpp-build

Cartographie, lecture seule : pourquoi Bridget s'affiche chauve dans l'éditeur de personnage quand le mod lpr_hair_pocolov (MODULE_DEFAULT_ENABLED: true) est présent, en 5.5 (mod chargé) comme en 5.8 (mod refusé). Je n'ai rien modifié en dehors de ce livrable. Je n'ai lancé ni le jeu, ni l'éditeur, ni aucun commandlet. Environnement vérifié : moteurs UE_5.5 et UE_5.8 installés (GameInstance.cpp lu dans les deux), copies 5.5 et 5.8 présentes, et les builds LifePlay_Build_5.5_DC4, _DC4_P10, _5.8_U7c et _U7c_GM présents.

1. Réponse courte

  • Le défaut ne vient pas de l'ordre d'insertion de EnsureDefaultEnabledModules (estimé, de façon forte). Cet ordre explique seulement l'affichage : Pocolov apparaît au-dessus de « Base Gameplay Module » (§ 4).
  • La cause est une reconstruction de GI.HairstyleTable par ULPHairstyleSubsystem::Sync(). Elle intervient après le préchargement des coiffures par le Blueprint et avant l'ouverture de l'éditeur. Sync() vide la table (EmptyValues) puis la recrée sans le champ SkeletalMesh, ce qui efface tous les maillages déjà chargés. GI.UpdateDefaultHair (femme, Daz) lit ensuite HairstyleTable[38].SkeletalMesh, c'est-à-dire ToulouseHR, sans le recharger. Le maillage vaut None, d'où le rendu chauve. Le journal montre Accessed None ... SkeletalMesh_2 dans UpdateDefaultHair, en 5.5 comme en 5.8.
  • Rôle du mod : il n'intervient que comme déclencheur. La reconstruction part dès que la « signature » de GI.ModEnableds change ; cette signature dépend de l'ordre. Avec Pocolov, les deux sessions où le défaut apparaît contiennent un tel changement entre l'écran titre et l'éditeur. Sans mod, les sessions archivées n'en contiennent pas.
  • Prédiction (à vérifier par l'essai du § 7) : sans aucun mod, ouvrir le gestionnaire de mods à l'écran titre d'une installation neuve, puis lancer New Game et cliquer Female, doit aussi donner une Bridget chauve.

2. Chaîne causale

# Maillon Niveau Preuve
1 L'événement Blueprint Event Init de GI (ReceiveInit) s'exécute avant l'initialisation des sous-systèmes. Quand ULPHairstyleSubsystem::Initialize démarre, GI.ModEnableds contient donc la valeur par défaut du Blueprint : GI.LoadSettings ne s'exécute qu'à New Game ou à Continue établi (moteur) ; estimé (absence de LoadSettings au démarrage) UE_5.5/.../GameInstance.cpp:99 ReceiveInit() puis :128 SubsystemCollection.Initialize ; 5.8 :100/:129. Les avertissements de PopulateRaws (ParseIntoArray_ReturnValue_29) n'apparaissent qu'à 06:36:28 (fermeture du gestionnaire) et à 06:36:32 (New Game), jamais à la frame 0 (P10/5.5/LifePlay.log, grep ParseIntoArray_ReturnValue_29 | cut -c1-32 | uniq -c)
2 À l'initialisation, EnsureDefaultEnabledModules ajoute Pocolov en fin de ModEnableds (AddValue). LastSignature est alors calculée et un premier Sync() construit la table : tous les SkeletalMesh valent None établi 5.8 LPHairstyleSubsystem.cpp:155-157, :412 ; 5.5 :154-156, :399. Journal 5.5 P10/5.5/LifePlay.log:857-871, journal 5.8 GM LifePlay.log:869-871
3 À l'écran titre, le personnage joueur (Person, isPlayer) précharge toutes les coiffures Daz dans GI.HairstyleTable. La chaîne est : affichage d'un widget Loading, minuterie de 0,2 s, GI.LoadMeshIfNeeded, pause de 0,3 s, GI.LoadDefaultTextures, pause de 0,3 s, puis une boucle ForEach HairstyleTable qui appelle GI.LoadHairMeshIfNeeded(i) pour chaque entrée Daz. Le widget Loading est retiré et la musique du titre démarre ensuite. LoadHairMeshIfNeeded charge le maillage par getSkeletalMeshFromPath et le réécrit dans la table (Array_Set) estimé (relevé de graphe ; le nom de l'événement d'entrée n'est pas relevé) L6f_dump_Person.txt:39484 (CustomEvent_99 → CreateWidget), :30568 (minuterie 0,2 s → CustomEvent_15), :31703 (isPlayer), :38980 (LoadDefaultTextures), :30598 (boucle sur HairstyleTable), :30615 (condition Daz), :30620 (LoadHairMeshIfNeeded), :30591 (Loading.RemoveFromParent), :39503 (SpawnSound2D → ThemeSong). L6f_dump_GI.txt:38472-38495 (LoadHairMeshIfNeeded : IsValid, getSkeletalMeshFromPath, Array_Set sur HairstyleTable)
4 GI.ModEnableds change à l'écran titre. Le cas observé est l'ouverture du gestionnaire de mods : GI.CheckIfModAlreadyFound insère à l'indice 0 chaque module absent de la liste, avec la case décochée. Ensuite, à chaque tick de 2 s, EnsureDefaultEnabledModules s'exécute puis la signature est comparée. Comme elle a changé, le sous-système journalise « liste des modules actifs modifiee, reconstruction » établi (code, journaux) ; estimé (origine du changement) L6f_dump_GI.txt:153-208 (Array_Insert, Index = '0' en :202-206). 5.8 LPHairstyleSubsystem.cpp:167-185, signature dépendante de l'ordre :354-370. 5.5 : interface UIOnly à 06:35:42.287 (frame 310), reconstruction à 06:35:42.689 (P10/5.5/LifePlay.log:6206). 5.8 GM : « gestionnaire, 1 mod(s) refuse(s) marque(s) » à 07:32:12.251 (:6248), reconstruction à 07:32:12.983 (:6250), puis trois autres (:6256, :6263, :6271)
5 Sync() efface tous les maillages chargés. En mode base, il appelle Table.EmptyValues() puis recrée chaque entrée avec seulement Bool, Daz et SkeletalMeshPath. Sans fichier de base, il réimporte le texte du Blueprint capturé au premier Sync(), où tous les maillages valent None. Aucun maillage n'est reporté établi (code) 5.8 LPHairstyleSubsystem.cpp:446-460 (base) et :486 (Blueprint) ; 5.5 :433-447 et :473. La table de base est « IDENTIQUE au Blueprint » (11_Audit_Optimisation.md:647-649) : la valeur par défaut du Blueprint ne contient donc aucun maillage, qui ne sont chargés qu'à l'exécution
6 Entre la reconstruction et l'éditeur, plus rien ne recharge l'indice 38 : le préchargement du maillon 3 ne se fait qu'une fois, à l'écran titre estimé ordre des journaux : reconstruction (5.5 :6206, 5.8 :6250-6272), New Game, OpenCharGen (5.5 :8307, 5.8 :8568)
7 Bouton Female → GI.PlayerChangeGender → GI.UpdateDefaultHair (CustomEvent_5). Selon CharacterBeingEdited.IsMale, puis selon Race == 'Daz', l'indice est fixe : femme Daz 38, femme non-Daz 20, homme Daz 50, homme non-Daz 24 (SelectInt). Les branches Daz appellent SetSkinnedAssetAndUpdate(HairSM, HairstyleTable[i].SkeletalMesh) sans LoadHairMeshIfNeeded. Seules les branches non-Daz chargent le maillage établi DOC-MOD_LPDumpFunction_20261007-083554.txt:3811 (CustomEvent_5), :3814-3818 (IfThenElse_104, IsMale), :20464 (IfThenElse_66 : then → CallFunction_661 directement, else → LoadHairMeshIfNeeded), :19484 (SelectInt A=38, B=20), :19497 (Race comparé à 'Daz'), :3831 (SetSkinnedAssetAndUpdate, NewMesh = BreakStruct_13.SkeletalMesh), :19938 (GetArrayItem_6 sur HairstyleTable). Le nœud PrintString 'Update Default Hair' est relié à IfThenElse_104 (:3814-3815)
8 HairstyleTable[38].SkeletalMesh vaut None : le maillage des cheveux est remplacé par rien, d'où le rendu chauve. La boucle sur les matériaux qui suit lit Materials sur ce None établi 5.5 P10/5.5/LifePlay.log:8377-8385 et 5.8 GM LifePlay.log:8637-8645 : Accessed None trying to read property SkeletalMesh_2_93573D... avec la pile ButtonFemale → PlayerChangeGender → UpdateDefaultHair. Dans le graphe de GI, seul cet événement utilise SMBool.SkeletalMesh comme objet cible (DOC-MOD...083554.txt:4255, :4269)
9 L'indice 38 correspond bien à « Toulouse » : /Game/Heavy/Daz/Bridget/Hairs/ToulouseHR.ToulouseHR, SEX: F, DAZ: true, morph ToulouseHR__AllExpand 0.45. Les coiffures Pocolov sont ajoutées en fin de table (indices 126 à 137) et ne décalent pas l'indice 38 établi LifePlay-master 5.8/Content/Modules/vin_Base/Hairstyles/vin_Base.lphair:161-164, :516 (fichier identique en 5.5, diff -q vide). Journal 5.5 :859-870
10 Les coiffures restent sélectionnables ensuite, parce que CharGen.PopulateHairstyles appelle LoadHairMeshIfNeeded pour chaque entrée de la table établi (graphe) L4-CX-B04_dump_apres_CharGen_EventGraph.txt:708-724, :2034

Les lignes set hair morph 0.0 Pocolov17__AllExpand (5.5, :8391) et ... ManeHair__AllExpand (5.8, :8670) arrivent 36 à 38 s après l'ouverture de l'éditeur. J'estime qu'elles correspondent au choix fait ensuite par l'utilisateur dans l'onglet coiffure (widget Hairstyle_C, non relevé) : elles ne jouent aucun rôle dans le défaut.

3. Pourquoi la reconstruction se produit avec le mod et pas sans (sessions archivées)

Comptage par journal (grep -c sur UpdateDefaultHair, reconstruction, ModManagerButton/« gestionnaire » et OpenCharGen, ordre des lignes) :

Session Mod Gestionnaire ouvert avant New Game Reconstruction avant l'éditeur Accessed None dans UpdateDefaultHair
P10/5.5/LifePlay.log (5.5 DC4_P10) Pocolov chargé oui (06:35:42) oui (:6206) oui (:8377)
LifePlay_Build_5.8_U7c_GM/.../LifePlay.log Pocolov refusé oui (07:32:12) oui (:6250-6272) oui (:8637)
LifePlay_Build_5.5_DC4 (5 journaux) aucun non (gestionnaire ouvert après l'éditeur, ou pas du tout) non : reconstruction après l'éditeur (backup-...07-18.46.57.log:20078, LifePlay.log:8778) non
LifePlay_Build_5.8_Development (2 journaux) aucun non non non

Dans les sessions sans mod, l'absence d'avertissement ne prouve pas que UpdateDefaultHair s'est exécuté : aucune pile PlayerChangeGender n'y figure. Ces sessions concordent avec la chaîne, mais ne la démontrent pas.

Contenu de ModEnableds dans GameSettings.sav (lu en octets, commande en § 9) :

Fichier Ordre du tableau (indice 0 en premier)
LifePlay_Build_5.5_DC4 vin_Pregnancy(T), vin_VanillaRealistic(T), vin_VanillaPorn(T), vin_NTR(F), vin_Base(T) : 5 entrées seulement
LifePlay_Build_5.5_DC4_P10 (après la session) 11 vin_* décochés, puis vin_Pregnancy, vin_VanillaRealistic, vin_VanillaPorn, vin_NTR, vin_Base, lpr_hair_pocolov(T)
LifePlay_Build_5.8_U7c_GM (après la session) 11 vin_*, puis vin_Pregnancy, vin_VanillaRealistic, vin_NTR, vin_Base, lpr_hair_pocolov(F), vin_VanillaPorn (ordre modifié par l'utilisateur)

Ma lecture (estimé, de façon forte) : la liste au démarrage est la liste par défaut du Blueprint, avec les 5 entrées de DC4. EnsureDefaultEnabledModules y ajoute Pocolov à la fin. À l'ouverture du gestionnaire, les 11 modules absents sont insérés à l'indice 0. On obtient exactement l'ordre enregistré dans DC4_P10. La signature change, ce qui déclenche la reconstruction.

Ce mécanisme ne dépend pas de Pocolov : sans mod, l'ouverture du gestionnaire ajouterait aussi les 11 modules (DC4 n'en enregistre que 5). Le même effet est attendu dans deux autres cas :

  • LoadSettings charge à New Game une liste enregistrée différente de la liste par défaut augmentée des modules par défaut ;
  • l'utilisateur coche, décoche ou réordonne un mod à l'écran titre.

Incertain : je n'ai pas vérifié la valeur par défaut de GI.ModEnableds (relevé CDO absent, § 8).

4. Ordre d'insertion de EnsureDefaultEnabledModules : ce qu'il explique

  • Le gestionnaire affiche ModEnableds à l'envers : indice = longueur − 1 − position affichée (23_Cycle_De_Vie_Mod.md:109). vin_Base, dernier élément du tableau, s'affiche donc en tête.
  • Le Blueprint insère les nouveaux modules à l'indice 0 (L6f_dump_GI.txt:202-206), donc en bas de l'affichage. Le C++ ajoute à la fin (LPHairstyleSubsystem.cpp:412), donc au-dessus de vin_Base. C'est le symptôme « Pocolov en tête » (établi). Cet emplacement est hors convention.
  • Conséquences : dans PopulateRaws, le premier fichier lu l'emporte (23_Cycle_De_Vie_Mod.md:111-116). Pocolov est donc lu après vin_Base, avec la priorité la plus basse. C'est sans effet pour ce mod : il ne contient que .lphair, .lpmod et CREDITS.txt. La garde de SortLoadOrder (ModuleIndex == 1 && ForUp, doc 23 :110) suppose vin_Base en position affichée 0 : avec Pocolov au-dessus, on peut seulement le redescendre. L'effet est mineur.
  • Aucune lecture par indice fixe de ModEnableds n'existe dans les graphes relevés. Les accès sont des boucles, Array_Insert 0, Array_Set à l'indice de boucle (ToggleMod), Length + Swap (SortLoadOrder) et RemoveItem : L6f_dump_GI.txt:175, :204, :24547, :41132, :41163, :50828-52633 ; DOC-MOD...083554.txt:1654-1681, :1738, :3392-3412, :3443. UI/ModuleTick compare 'vin_Base' par nom (DOC-MOD...083338.txt:178, :204, :248). Aucun code ne suppose donc vin_Base en [0] (établi pour les graphes relevés : GI, ModuleTick).
  • Changer l'emplacement d'insertion (indice 0, juste avant vin_Base, ou en fin) ne corrige pas le défaut. Le gestionnaire, LoadSettings, une case ou une flèche modifient la liste quel que soit cet emplacement.

Origine : la règle MODULE_DEFAULT_ENABLED vient de CHG-102, lot H3a (et non H3d), commit 3c2da7a (Journal_Modifications.md:1078, 16_H3_Mods_Paquets.md:101-104). La reconstruction au changement de signature date de H1 (CHG-084, cad1649), le vidage EmptyValues de H2 (CHG-089, bac557d). Le défaut est donc latent depuis H1 ; Pocolov ne fait que le rendre visible.

5. Correctifs proposés (C++, sans Blueprint) — décision de l'orchestrateur et de l'utilisateur

Id Correctif Effet Risques
C1 (cause) Dans Sync(), conserver les maillages déjà chargés. Avant l'étape 1, relever la correspondance chemin → maillage des entrées dont SkeletalMesh n'est pas nul. Après l'étape 2, réaffecter le maillage aux entrées de même chemin. Les deux branches (base et Blueprint) et les entrées de modules sont couvertes la reconstruction devient sans effet pour le chargement paresseux du Blueprint : l'indice 38 garde ToulouseHR faible : aucun nouveau chargement, objets déjà référencés par GI ; ignorer la clé vide (entrées NONE). Les coiffures d'un mod décoché disparaissent avec leurs entrées. À valider : relecture du chemin par GetFieldText (FName exporté), identique à celui de Known (:495)
C2 (déclencheur) Calculer la signature uniquement sur les modules qui possèdent des coiffures ou une pilosité (Defs, Textures), avec leur état coché, triée pour ne plus dépendre de l'ordre ouvrir le gestionnaire, réordonner, cocher un mod texte ou recharger les réglages ne reconstruit plus la table faible ; ne couvre pas la case d'un mod de coiffures (reconstruction légitime) : à combiner avec C1
C3 (affichage, facultatif) EnsureDefaultEnabledModules : Helper.InsertValues(0) au lieu de AddValue(), comme le fait CheckIfModAlreadyFound Pocolov s'affiche sous vin_Base ; priorité de lecture d'un mod normal sans effet sur le défaut ; un module par défaut qui aurait des fichiers texte homonymes de vin_Base les imposerait alors (aucun aujourd'hui : lp_Revival n'existe plus en 5.5)
écarté Recharger en C++ les coiffures Daz après Sync() (LoadObject) — chargement synchrone d'environ 90 maillages, saccade, et duplication de la logique du Blueprint

Esquisse de C1. Les API ont été vérifiées dans UnrealType.h : 5.5 :2653, :2658 ; 5.8 :2896, :2901.

// Sync(), apres la creation de Table (:439) et avant l'etape 1
const FObjectPropertyBase* MeshProp = CastField<FObjectPropertyBase>(FindField(Struct, TEXT("SkeletalMesh")));
TMap<FString, UObject*> Loaded;
for (int32 i = 0; MeshProp && i < Table.Num(); ++i)
    if (UObject* Mesh = MeshProp->GetObjectPropertyValue_InContainer(Table.GetRawPtr(i)))
        Loaded.Add(GetFieldText(Struct, Table.GetRawPtr(i), TEXT("SkeletalMeshPath")), Mesh);
// ... etapes 1 et 2 inchangees ...
// apres l'etape 2 (avant le journal :546)
for (int32 i = 0; MeshProp && i < Table.Num(); ++i)
    if (UObject* const* Mesh = Loaded.Find(GetFieldText(Struct, Table.GetRawPtr(i), TEXT("SkeletalMeshPath"))))
        MeshProp->SetObjectPropertyValue_InContainer(Table.GetRawPtr(i), *Mesh);

Avec -LPVerifyHairstyles, la comparaison au Blueprint se fait avant la réaffectation : elle reste valable.

6. Points (a), (b), (c) de la méthode

  • (a) Construction de ModEnableds : la valeur par défaut du Blueprint au démarrage (estimé) ; LoadSettings (New Game, Continue) la remplace par celle de GameSettings.sav, puis ReadModLauncher et removeDeletedMods (23_Cycle_De_Vie_Mod.md:200-224) ; le gestionnaire appelle CheckIfModAlreadyFound, qui insère à 0 (L6f_dump_GI.txt:153-208) ; ToggleMod et SortLoadOrder la modifient ensuite ; le C++ ajoute en fin (:412). IsModEnabled parcourt la liste (L6f_dump_GI.txt:24538-24560).
  • (b) Lecture par indice : aucune (§ 4).
  • (c) Coiffure par défaut : indice fixe dans UpdateDefaultHair (38 pour une femme Daz = ToulouseHR), lu sans chargement dans la branche Daz. Le maillage reste vide parce que Sync() a vidé la table après le préchargement du titre (§ 2, maillons 3, 5 et 7).

7. Essai décisif (utilisateur, 3 étapes, sans mod)

Sur une copie jetable de D:\Game\LifePlay_Build_5.8_U7c. Cette copie n'a aucun mod dans Mods, pas de module Pocolov et aucun SaveGames : c'est le cas d'un premier lancement.

  1. Lancer le jeu. À l'écran titre, ouvrir le gestionnaire de mods puis le fermer sans rien changer.
  2. New Game, puis bouton Female (Bridget).
  3. Relever deux choses : Bridget est-elle chauve ? Saved\Logs\LifePlay.log contient-il « Coiffures : liste des modules actifs modifiee, reconstruction » avant OpenCharGen, puis Accessed None ... SkeletalMesh_2_93573D... avec UpdateDefaultHair dans la pile ?

Verdict :

  • chauve avec ces deux lignes : la cause est la reconstruction ; ni Pocolov ni l'ordre d'insertion ne sont en jeu. Correctif C1, éventuellement avec C2.
  • ToulouseHR visible et aucune reconstruction avant l'éditeur : la chaîne est réfutée sur le maillon 4. Je reprendrais alors avec le relevé CDO du § 8.

Contre-essai facultatif, sur une nouvelle copie jetable : New Game puis Female sans ouvrir le gestionnaire. ToulouseHR attendue. Contrôle supplémentaire : dans l'état chauve, ouvrir l'onglet coiffure (chargement de toutes les coiffures), cliquer Male puis Female. ToulouseHR attendue, ce qui montre que l'indice est juste et que seul le chargement manquait.

8. Incertitudes et relevés manquants (non lancés)

  • LPDumpFunction n'imprime pas le nom des événements personnalisés (LPDumpFunctionCommandlet.cpp:18-30). CustomEvent_5 = UpdateDefaultHair est établi par le PrintString attaché et par la pile d'exécution. L'événement d'entrée du préchargement (CustomEvent_99, Person) reste anonyme. La chaîne n'en a pas besoin.
  • La valeur par défaut de GI.ModEnableds et le SkeletalMesh à None de la valeur par défaut de HairstyleTable[38] sont estimés. Commande pour les établir (copie 5.5, éditeur fermé) : .\Tools\RunCommandlet.ps1 -Lot U7-T11 -Commandlet LPDumpCDO -Args '-Class=/Game/BPs/GI.GI_C -Out=Docs\Migration\etat\U58\U7\U7-T11_GI_CDO.txt -Props=ModEnableds,HairstyleTable'
  • Lecture exacte d'une sauvegarde, en complément de l'extraction en octets : .\Tools\RunCommandlet.ps1 -Lot U7-T11 -Commandlet LPDumpSave -Args '-File=D:\Game\LifePlay_Build_5.5_DC4_P10\Windows\LifePlay\Saved\SaveGames\GameSettings.sav -Out=Docs\Migration\etat\U58\U7\U7-T11_GameSettings_P10.txt -Props=ModEnableds'
  • Le journal 5.8 de la session avec l'éditeur n'existe que dans le build (LifePlay_Build_5.8_U7c_GM\Windows\LifePlay\Saved\Logs\LifePlay.log, 9 712 lignes ; le fichier a grossi pendant le relevé, de 660 657 à 666 184 octets, jeu probablement encore ouvert). La copie archivée U7\T9\LifePlay.log s'arrête à 6 275 lignes, avant l'éditeur. Il faut l'archiver avant le prochain lancement de cette copie.

9. Commandes utilisées (lecture seule)

  • grep -n sur les journaux cités : Coiffures :, Modules :, reconstruction, UpdateDefaultHair, SkeletalMesh_2_93573D, ButtonFemale, ModManagerButton, OpenCharGen.
  • Balayage de 41 journaux de builds : for d in D:/Game/LifePlay_Build_*; do ... grep -c ....
  • Extraction en octets de ModEnableds dans GameSettings.sav : python -I -c "re.finditer(rb'(vin_[A-Za-z]+|lpr?_[A-Za-z_]+)\x00', d) ...". Le drapeau booléen se lit dans l'en-tête de propriété qui suit IsEnabled : octet 0x10 = vrai.
  • Parcours des relevés : awk par nom de nœud dans DOC-MOD_LPDumpFunction_20261007-083554.txt, L6f_dump_Person.txt, L6f_dump_GI.txt et L4-CX-B04_dump_apres_CharGen_EventGraph.txt.
  • git log -S "EnsureDefaultEnabledModules", git log -S "liste des modules actifs modifiee", git log -S "Table.EmptyValues".