20 KiB
Fiche U7-T12 — Niveau N3 — Modèle prévu lp-impl (Opus xhigh) | utilisé Opus 5.5 (claude-opus-5-5) — Agent lp-impl — Skills ue-cpp-build, sk-protocol (présents aussi dans le contexte : lp-build, lp-ue-compat)
U7-T12 — IMPL : coiffure par défaut perdue à la reconstruction (correctifs C1 + C2)
Copie modifiée : D:\Game\LifePlay-master 5.8 (branche ue5.8), seulement Source\LifePlay\LPHairstyleSubsystem.cpp et
.h. La copie 5.5 (main) n'a pas été touchée : git -C "D:/Game/LifePlay-master 5.5" status --short est vide.
Je n'ai rien compilé ni lancé, je n'ai fait aucun commit et je n'ai rien supprimé. Je n'ai touché à aucun Blueprint.
1. Résultat
PASS-STATIC (proposé, non compilé). C1 et C2 sont écrits. La garde U7-T7 est intacte : le bloc FLPModGuard::IsRefused
et la ligne Coiffures : %s ignore (mod refuse) sont en LPHairstyleSubsystem.cpp:241-251, inchangés. C3 n'a pas été
fait. Je n'ai touché ni à LPModGuard ni à LPModInfoSubsystem. Aucun critère d'arrêt n'est atteint (§ 2 et § 6).
2. Vérifications préalables (établi)
| Point | Constat | Source |
|---|---|---|
| Fins de ligne et encodage avant modification | .cpp en ASCII avec LF, sans BOM ; .h en UTF-8 avec LF, sans BOM (seul caractère non ASCII : §, ligne 1) ; dernière ligne terminée par LF. .gitattributes vaut * -text |
file, grep -c $'\r' = 0, od -c des 3 premiers octets, git ls-files --eol → i/lf w/lf attr/-text |
| État de départ | HEAD + garde U7-T7 (+15 lignes). MD5 avant T12 : .cpp 78e185d11c9eabe0275a85d0a9799ecf, .h 740e790d11592016d6ea4a0568b3564e. L'instantané D:\Game\LifePlay_Backup_5.5\U7\Source\LifePlay\ contient la version d'origine sans la garde (MD5 db610dcf…, 347de153…, manifest.csv) |
git diff, md5sum |
Champs de la structure SMBool (entrée de HairstyleTable) |
8 champs : SkeletalMesh_2, Bool_5, Strings_11, Strings2_15, Bool2_19, Bool3_21, SkeletalMeshPath_25, Daz_27 |
etat/DOC-MOD_LPDumpFunction_20261007-083554.txt:19944-19951, L6f_dump_GI.txt:38472-38520 |
| Champ objet chargé | Seul SkeletalMesh est une référence d'objet dure (catégorie object). Aucune catégorie softobject n'apparaît dans la table de noms de Content/Structs/SMBool.uasset. SoftObjectProperty y vient de FStructVariableDescription::SubCategoryObject (TSoftObjectPtr, UE_5.8/.../UnrealEd/Classes/UserDefinedStructure/UserDefinedStructEditorData.h:58). SkeletalMeshPath est un FName (NotEqual_NameName, L6f_dump_GI.txt:38504) |
extraction des chaînes imprimables de SMBool.uasset (python -I) |
| Écriture du maillage par le Blueprint | GI.LoadHairMeshIfNeeded lit l'entrée (Break) puis la réécrit (Make + Array_Set) en recopiant tous les champs. Seul SkeletalMesh change (getSkeletalMeshFromPath). Un IsValid en tête évite le rechargement si le champ est déjà rempli. Le chemin reste donc la clé de l'entrée |
L6f_dump_GI.txt:38472-38520 |
| API moteur (5.5 et 5.8) | FObjectPropertyBase::GetObjectPropertyValue_InContainer est publique et virtuelle (5.5 UnrealType.h:2653, 5.8 :2896), surchargée publiquement dans FObjectProperty (5.5 :2933, 5.8 :3191). SetObjectPropertyValue_InContainer est publique (5.5 :2658, 5.8 :2901) et non masquée dans TFObjectPropertyBase ni dans FObjectProperty (5.8 :3056-3195). Implémentation 5.8 : PropertyBaseObject.cpp:706-716 (seul refus : valeur nulle sur un champ CPF_NonNullable, cas exclu ici puisque seules les valeurs non nulles sont rendues). TFieldIterator<FObjectProperty> est défini en 5.5 UnrealType.h:6764 et en 5.8 :7193, et Epic l'utilise dans Actor.cpp:945 (5.8) et :861 (5.5) |
sources moteur installées |
Le premier critère d'arrêt n'est pas atteint : le champ objet est lu et écrit proprement par réflexion, avec des API communes à 5.5 et 5.8 et sans garde de version.
3. Diff par fonction (numéros de ligne du fichier modifié)
3.1 ULPHairstyleSubsystem::ScanFiles — .cpp:260-270 (C2)
@@ -257,6 +257,17 @@
ParseFile(Relative, Module, Text);
}
+ // Signature (lot U7-T12, C2) : modules dont Sync lit l'etat, c'est-a-dire ceux qui apportent des coiffures ou une
+ // pilosite (le fichier de base est applique quel que soit l'etat de son module). Un mod refuse n'y figure pas.
+ for (const FHairDef& Def : Defs)
+ {
+ HairModules.AddUnique(Def.Module);
+ }
+ for (const FTextureDef& Def : Textures)
+ {
+ HairModules.AddUnique(Def.Module);
+ }
+ HairModules.Sort();
if (Files.Num() > 0)
3.2 ULPHairstyleSubsystem::EnabledSignature — .cpp:365-377 (C2)
@@ -353,18 +364,14 @@
FString ULPHairstyleSubsystem::EnabledSignature() const
{
- const UObject* GI = GetGameInstance();
- const UScriptStruct* Struct = nullptr;
- const FArrayProperty* Prop = StructArray(GI, ModEnabledsName, Struct);
- if (!Prop)
- {
- return FString();
- }
+ // Lot U7-T12, C2 : seul compte l'etat des modules de coiffures ou de pilosite, dans un ordre fixe, avec la regle de
+ // Sync (IsModuleEnabled : module absent = inactif). Ouvrir le gestionnaire de mods (GI.CheckIfModAlreadyFound insere
+ // les modules absents, decoches), reordonner la liste, cocher un module sans coiffure ou recharger les reglages ne
+ // reconstruit plus la table ; cocher ou decocher un module de coiffures la reconstruit toujours.
FString Signature;
- FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
- for (int32 i = 0; i < Helper.Num(); ++i)
+ for (const FString& Module : HairModules)
{
- Signature += GetFieldText(Struct, Helper.GetRawPtr(i), TEXT("UniqueID")) + TEXT("=") + GetFieldText(Struct, Helper.GetRawPtr(i), TEXT("IsEnabled")) + TEXT(";");
+ Signature += Module + (IsModuleEnabled(Module) ? TEXT("=1;") : TEXT("=0;"));
}
return Signature;
}
3.3 ULPHairstyleSubsystem::Sync — .cpp:440, :450-476 (relevé), :583-608 (restitution) (C1)
@@ -430,6 +437,7 @@
return;
}
+ const bool bRebuild = bCaptured; // faux au premier passage (Initialize) : construction, pas reconstruction
if (!bCaptured)
@@ -439,6 +447,34 @@
FScriptArrayHelper Table(TableProp, TableProp->ContainerPtrToValuePtr<void>(GI));
FScriptMapHelper Map(MapProp, MapProp->ContainerPtrToValuePtr<void>(GI));
+ // 0. Lot U7-T12, C1 : objets deja charges dans la table, par chemin. Le Blueprint remplit SkeletalMesh a la demande
+ // (GI.LoadHairMeshIfNeeded, prechargement des coiffures Daz a l'ecran titre) et GI.UpdateDefaultHair le lit ensuite
+ // sans recharger : la reconstruction ne doit pas l'effacer. Tous les champs objet de la structure sont releves
+ // (SkeletalMesh seul dans SMBool) ; ils sont rendus apres l'etape 2 aux entrees de meme chemin, base et modules.
+ TArray<const FObjectProperty*> ObjectFields;
+ for (TFieldIterator<FObjectProperty> It(Struct); It; ++It)
+ {
+ ObjectFields.Add(*It);
+ }
+ TMap<FString, TArray<UObject*>> Loaded;
+ for (int32 i = 0; i < Table.Num() && ObjectFields.Num() > 0; ++i)
+ {
+ const void* Entry = Table.GetRawPtr(i);
+ TArray<UObject*> Values;
+ bool bAny = false;
+ for (const FObjectProperty* Field : ObjectFields)
+ {
+ UObject* Value = Field->GetObjectPropertyValue_InContainer(Entry);
+ Values.Add(Value);
+ bAny |= Value != nullptr;
+ }
+ const FString Path = GetFieldText(Struct, Entry, TEXT("SkeletalMeshPath"));
+ if (bAny && !Path.IsEmpty()) // entrees NONE (chemin vide) et coiffures jamais chargees : rien a rendre
+ {
+ Loaded.Add(Path, MoveTemp(Values));
+ }
+ }
+
// 1. Base : fichier MODE: BASE reconstruit dans son ordre, sinon valeurs du Blueprint.
@@ -543,6 +579,33 @@
UE_LOG(LogLifePlay, Log, TEXT("Coiffures : %s ajoutee (index %d, %s, module %s)"), ...);
}
+
+ // 3. Lot U7-T12, C1 : objets releves a l'etape 0 rendus aux entrees reconstruites de meme chemin. Une coiffure d'un
+ // module desactive n'a plus d'entree : son maillage n'est pas rendu (compte "sans entree").
+ int32 Kept = 0;
+ TSet<FString> KeptPaths;
+ for (int32 i = 0; i < Table.Num() && Loaded.Num() > 0; ++i)
+ {
+ void* Entry = Table.GetRawPtr(i);
+ const FString Path = GetFieldText(Struct, Entry, TEXT("SkeletalMeshPath"));
+ if (const TArray<UObject*>* Values = Loaded.Find(Path))
+ {
+ for (int32 Field = 0; Field < ObjectFields.Num(); ++Field)
+ {
+ if ((*Values)[Field])
+ {
+ ObjectFields[Field]->SetObjectPropertyValue_InContainer(Entry, (*Values)[Field]);
+ }
+ }
+ KeptPaths.Add(Path);
+ ++Kept;
+ }
+ }
+ if (bRebuild)
+ {
+ UE_LOG(LogLifePlay, Display, TEXT("Coiffures : %d maillage(s) conserve(s) a la reconstruction (%d chemin(s) charge(s) avant, %d sans entree)"),
+ Kept, Loaded.Num(), Loaded.Num() - KeptPaths.Num());
+ }
UE_LOG(LogLifePlay, Display, TEXT("Coiffures : base %d entree(s) ..."), ...);
Le relevé (étape 0) précède le vidage, dans les deux modes : base (EmptyValues) et Blueprint (ImportText_Direct du
texte capturé). La restitution (étape 3) vient après l'ajout des modules. Elle couvre donc les entrées de base et celles
des modules. La vérification -LPVerifyHairstyles (étape 1) se fait avant la restitution : elle compare toujours la
table de base au Blueprint, comme avant.
3.4 LPHairstyleSubsystem.h — commentaire d'en-tête :28-31, déclaration :89, membre :100-101
@@ -27,3 +27,6 @@
-// Les modules desactives dans GI.ModEnableds sont ignores ; la table est reconstruite quand cette liste change.
+// Les modules desactives dans GI.ModEnableds sont ignores ; la table est reconstruite quand l'etat (actif ou non) d'un
+// module qui apporte des coiffures ou une pilosite change, pas pour un autre changement de la liste (ordre, module sans
+// coiffure, gestionnaire de mods ouvert : lot U7-T12, C2). La reconstruction rend aux entrees de meme chemin les maillages
+// deja charges par le Blueprint (GI.LoadHairMeshIfNeeded) : GI.UpdateDefaultHair les lit sans recharger (lot U7-T12, C1).
@@ -85,2 +88,3 @@
void SyncTextureLists(UObject* GI);
+ /** Signature : etat actif des modules de HairModules, dans l'ordre trie (independante de l'ordre de GI.ModEnableds). */
FString EnabledSignature() const;
@@ -95,2 +99,4 @@
FString LastSignature;
+ /** Modules (hors fichier de base) qui apportent des coiffures ou une pilosite, tries : seuls a compter dans la signature. */
+ TArray<FString> HairModules;
Initialize, Tick, IsModuleEnabled, EnsureDefaultEnabledModules, ParseFile et SyncTextureLists sont inchangés.
ModEnabledsName reste utilisé, par IsModuleEnabled et EnsureDefaultEnabledModules.
4. git diff --stat
Par rapport à HEAD (git -C "D:/Game/LifePlay-master 5.8" diff --stat -- Source/LifePlay/LPHairstyleSubsystem.*), garde U7-T7 comprise :
Source/LifePlay/LPHairstyleSubsystem.cpp | 96 ++++++++++++++++++++++++++++----
Source/LifePlay/LPHairstyleSubsystem.h | 10 +++-
2 files changed, 95 insertions(+), 11 deletions(-)
Part de T12 seule (git diff --no-index --stat contre la copie d'avant T12, dans le scratchpad de session) :
.cpp 73 insertions(+), 10 deletions(-) ; .h 7 insertions(+), 1 deletion(-).
Lignes : .cpp 619 → 682, .h 112 → 118.
MD5 après T12 : .cpp 5b7d8d5b2cb9c60aa775ecf3470685ab, .h 1edf6a9381210def5a2fda09c0315f3f.
git status --short de la copie 5.8 : la liste des fichiers modifiés est la même qu'avant T12 (U7-T7/T9). Aucun fichier nouveau.
5. Fins de ligne et encodage (vérifiés après modification)
grep -c $'\r' = 0 pour les deux fichiers. file donne .cpp en « ASCII text » et .h en « UTF-8 text », comme avant.
Les fichiers ne commencent pas par un BOM (premiers octets //) et se terminent par un LF. git ls-files --eol donne
i/lf w/lf attr/-text. Le texte ajouté est entièrement en ASCII.
6. Comportement attendu (estimé, raisonnement sur le code)
- C2 :
HairModulesregroupe les modules non refusés qui ont au moins une définitionHAIRSTYLEou de pilosité dans un.lphairnon base. Dans la copie 5.8 et dans le buildU7csans mod, il n'existe queContent/Modules/vin_Base/Hairstyles/vin_Base.lphair, avecMODE: BASE(find … -iname "*.lphair").HairModulesest donc vide, la signature vaut""en permanence et aucune reconstruction n'a lieu : ni à l'ouverture ou à la fermeture du gestionnaire, ni quand on coche un module texte, ni àLoadSettings. La coiffure préchargée à l'écran titre reste dans la table. - Module de coiffures coché ou décoché : son état entre dans la signature par
IsModuleEnabled, la règle même deSync. Le changement déclenche la reconstruction, etSyncsaute ses définitions s'il est décoché : ses coiffures disparaissent toujours. Le second critère d'arrêt n'est pas atteint. Ses maillages relevés n'ont plus d'entrée : ils sont comptés « sans entrée » et ne sont pas rendus. - Mod refusé par la garde (Pocolov en 5.8) : ses fichiers ne sont pas lus. Il ne figure donc ni dans
Defsni dansHairModules, et la case du gestionnaire ne reconstruit rien. La ligneCoiffures : lpr_hair_pocolov ignore (mod refuse)est inchangée. - C1 : à chaque reconstruction, chaque entrée dont le chemin existait déjà retrouve son
SkeletalMesh. Avec un module de coiffures accepté, l'indice 38 (ToulouseHR) garde donc son maillage après un basculement.LoadHairMeshIfNeededvoit ensuite un champ valide et ne recharge rien.
Journal attendu
- Démarrage : inchangé (
Coiffures : N fichier(s) .lphair ...,Coiffures : base ...). La nouvelle ligne n'apparaît pas au premierSync(bRebuildfaux). - À chaque reconstruction, juste après
Coiffures : liste des modules actifs modifiee, reconstruction(niveau Log) :LogLifePlay: Display: Coiffures : N maillage(s) conserve(s) a la reconstruction (M chemin(s) charge(s) avant, K sans entree), puis la ligneCoiffures : base ...habituelle. - Build
U7csans mod : aucune de ces deux lignes, quelle que soit la manipulation du gestionnaire. Aucune ligneAccessed None ... SkeletalMesh_2_93573D...avecUpdateDefaultHairdans la pile.
7. Essai proposé (après le BUILD par lp-builder et un nouveau build empaqueté ; à faire par l'utilisateur)
Le build D:\Game\LifePlay_Build_5.8_U7c contient l'ancienne DLL. L'essai demande un build empaqueté neuf qui contient
T12 (Tools\BuildDebug.ps1, dossier neuf, Mods\ vide) ou, à défaut, une copie jetable mise à jour.
- Sans mod, sans
SaveGames: à l'écran titre, ouvrir le gestionnaire de mods puis le fermer sans rien changer. Faire New Game puis Female. Attendu : Bridget coiffée (ToulouseHR) ;LifePlay.logsansreconstruction, sansmaillage(s) conserve(s), sansAccessed None ... SkeletalMesh_2_93573D. - Même chose en cochant puis décochant un module texte (
vin_NTRpar exemple) avant New Game. Attendu identique. - (C1, facultatif) Le build sans mod ne déclenche aucune reconstruction : C1 n'y est donc pas exercé. Pour le voir
à l'œuvre, il faut un module de coiffures accepté par la garde : coché puis décoché à l'écran titre, après le
préchargement. Attendu :
N maillage(s) conserve(s)avec N proche du nombre de coiffures Daz préchargées, puis Bridget coiffée.
8. Écarts à la SPEC et à la fiche
- E1 (forme, généralisation demandée) : l'esquisse de U7-T11 visait le seul champ
SkeletalMesh, parFindField(Struct, "SkeletalMesh"). J'ai relevé tous les champsFObjectPropertyde la structure, parTFieldIterator<FObjectProperty>, comme le demande la décision C1 (« tout autre champ objet chargé »). SurSMBool, on n'obtient queSkeletalMesh(§ 2). Le message du journal parle de « maillage(s) » et compte des entrées rendues : c'est égal au nombre de maillages tant queSkeletalMeshest le seul champ objet. - E2 (ajout) : la ligne de journal précise aussi
M chemin(s) charge(s) avant, K sans entree, pour rendre visible la disparition des maillages d'un module décoché. Elle n'est émise qu'en reconstruction, pas au premierSync. - E3 (C2, périmètre de la signature) : le module du fichier
MODE: BASEn'entre pas dans la signature.Syncapplique la base sans regarder l'état de ce module (code inchangé, étape 1,.cpp:478-524) : sa case ne changeait déjà rien au résultat. L'état retenu est l'état effectif (IsModuleEnabled: absent = inactif), plutôt que le texte brutIsEnabled. Un module de coiffures absent de la liste puis inséré décoché par le gestionnaire ne reconstruit donc plus rien. - E4 (instantané) : l'instantané
Backup_5.5\U7conserve la version d'origine, sans la garde. L'état d'avant T12 (avec la garde T7) n'a pas d'instantané durable. Je n'en ai pas créé dansBackup_5.5, qui est hors des chemins autorisés. Retour arrière : appliquer à l'envers le diff du § 3, MD5 attendus d'avant T12 au § 2. - Rien d'autre : pas de C3, aucune modification de
LPModGuard, deLPModInfoSubsystemni d'un Blueprint, pas de compilation.
9. Incertitudes et risques
- Incertain : compilation. Le code est « proposé, non compilé ». À établir par le BUILD 5.8 (
Tools\Build.ps1) : on attend 0 erreur et aucun avertissement nouveau dansLPHairstyleSubsystem.cpp. Les noms locaux (Field,Entry,Path,Values) sont déclarés dans des portées disjointes et ne masquent aucun membre de la classe (relu). Le risque d'un avertissement de masquage est faible. - Estimé : pas de ramasse-miettes pendant
Sync(). Entre le vidage et la restitution, les maillages ne sont tenus que par des pointeurs bruts locaux.Sync()est synchrone sur le fil de jeu et n'appelle aucun chargement (registre d'assets,DoesPackageExist,ImportTextd'un texte où les maillages valentNone) : aucune collecte n'est attendue. La restitution passe parSetObjectPropertyValue_InContainer(affectation d'unTObjectPtr). - Estimé : en éditeur, une reconstruction fortuite (provoquée par n'importe quel changement de la liste) pouvait
rattraper des coiffures de module encore absentes du registre au premier
Sync. Elle n'a plus lieu. Le repli « paquet présent » (.cpp:549-554) ajoute déjà ces coiffures au premier passage. Le risque est faible et ne touche que l'éditeur. - Hors périmètre, pour information :
LPDirectControlSubsystem.cpp:198-215et:626-635calculent leur propre signature sur toute la listeModEnableds, ordre compris, et ré-indexent les invites à chaque changement. Je n'ai constaté aucune perte d'objet chargé à cet endroit. Je n'y ai pas touché.
10. Compte rendu sk-protocol
[sk-protocol] Skills dans le contexte : sk-protocol, ue-cpp-build, lp-build, lp-ue-compat.
- Résultat : réussi en statique (PASS-STATIC). C1 et C2 sont écrits dans la copie 5.8 seulement, la garde U7-T7 est intacte.
- Preuves : diff au § 3 ; API à
UnrealType.h5.5:2653/:2658/:2933/:6764et 5.8:2896/:2901/:3191/:7193,PropertyBaseObject.cpp:706-716; structure àDOC-MOD…083554.txt:19944-19951etL6f_dump_GI.txt:38472-38520. - Chiffres :
git diff --stat95+/11- au total, dont T12 :.cpp73+/10-,.h7+/1- ; 0 CR ; 1 seul.lphair(base) dansContent/Modules. - Incertitudes : la compilation (non faite, interdite) ; C1 n'est pas exercé par l'essai sans mod (§ 7.3).
- Écarts : E1 à E4 (§ 8). Fichiers écrits :
D:\Game\LifePlay-master 5.8\Source\LifePlay\LPHairstyleSubsystem.cpp,D:\Game\LifePlay-master 5.8\Source\LifePlay\LPHairstyleSubsystem.h, ce livrable. - Suite : lp-builder doit compiler la copie 5.8 (
Tools\Build.ps1 -Lot U7-T12), puis l'utilisateur doit faire l'essai du § 7 sur un build empaqueté neuf ; état : code écrit, non compilé, non commité.