diff --git a/Content/BPs/Birdview.uasset b/Content/BPs/Birdview.uasset index aeafc34..7b94a02 100644 Binary files a/Content/BPs/Birdview.uasset and b/Content/BPs/Birdview.uasset differ diff --git a/Content/BPs/GI.uasset b/Content/BPs/GI.uasset index 2561574..4cd2903 100644 Binary files a/Content/BPs/GI.uasset and b/Content/BPs/GI.uasset differ diff --git a/Content/BPs/Person.uasset b/Content/BPs/Person.uasset index 5618e71..652c412 100644 Binary files a/Content/BPs/Person.uasset and b/Content/BPs/Person.uasset differ diff --git a/Content/BPs/Room.uasset b/Content/BPs/Room.uasset index 0c671c8..5391254 100644 Binary files a/Content/BPs/Room.uasset and b/Content/BPs/Room.uasset differ diff --git a/Content/BPs/StreetMap_Static.uasset b/Content/BPs/StreetMap_Static.uasset index 5e4e686..6bd6e00 100644 Binary files a/Content/BPs/StreetMap_Static.uasset and b/Content/BPs/StreetMap_Static.uasset differ diff --git a/Content/UI/Action.uasset b/Content/UI/Action.uasset index 906f074..179245c 100644 Binary files a/Content/UI/Action.uasset and b/Content/UI/Action.uasset differ diff --git a/Content/UI/CharGen.uasset b/Content/UI/CharGen.uasset index a5144b0..03c89c3 100644 Binary files a/Content/UI/CharGen.uasset and b/Content/UI/CharGen.uasset differ diff --git a/Content/UI/ContactMenu.uasset b/Content/UI/ContactMenu.uasset index 4dce3f4..fe215ab 100644 Binary files a/Content/UI/ContactMenu.uasset and b/Content/UI/ContactMenu.uasset differ diff --git a/Content/UI/Cursor.uasset b/Content/UI/Cursor.uasset index e7014ed..eb998d9 100644 Binary files a/Content/UI/Cursor.uasset and b/Content/UI/Cursor.uasset differ diff --git a/Content/UI/Inventory.uasset b/Content/UI/Inventory.uasset index ca7f719..6a7b7f2 100644 Binary files a/Content/UI/Inventory.uasset and b/Content/UI/Inventory.uasset differ diff --git a/Content/UI/Inventory_Cloth.uasset b/Content/UI/Inventory_Cloth.uasset index 18fb56e..034555c 100644 Binary files a/Content/UI/Inventory_Cloth.uasset and b/Content/UI/Inventory_Cloth.uasset differ diff --git a/Content/UI/MainMenu.uasset b/Content/UI/MainMenu.uasset index 5742dee..83fad7e 100644 Binary files a/Content/UI/MainMenu.uasset and b/Content/UI/MainMenu.uasset differ diff --git a/Content/UI/Objective.uasset b/Content/UI/Objective.uasset index d944b16..9504a8c 100644 Binary files a/Content/UI/Objective.uasset and b/Content/UI/Objective.uasset differ diff --git a/Content/UI/Recommend.uasset b/Content/UI/Recommend.uasset index 8373073..9847674 100644 Binary files a/Content/UI/Recommend.uasset and b/Content/UI/Recommend.uasset differ diff --git a/Content/UI/StatAdjust.uasset b/Content/UI/StatAdjust.uasset index 8ca25d3..8cad509 100644 Binary files a/Content/UI/StatAdjust.uasset and b/Content/UI/StatAdjust.uasset differ diff --git a/Content/UI/StatNumber.uasset b/Content/UI/StatNumber.uasset index e55ff61..10e9f5e 100644 Binary files a/Content/UI/StatNumber.uasset and b/Content/UI/StatNumber.uasset differ diff --git a/Content/UI/TopRight.uasset b/Content/UI/TopRight.uasset index acd9b16..319bbae 100644 Binary files a/Content/UI/TopRight.uasset and b/Content/UI/TopRight.uasset differ diff --git a/Docs/Migration/Journal_Modifications.md b/Docs/Migration/Journal_Modifications.md index 10408d1..aad9353 100644 --- a/Docs/Migration/Journal_Modifications.md +++ b/Docs/Migration/Journal_Modifications.md @@ -325,3 +325,20 @@ entre 4.27 et 5.5 ; la divergence est dans le moteur. Preuves : `Saved/Logs/portage/L5c_partie_utilisateur2_20260929.log`, `etat/L5-diag_dump55exec_GI_GeneratePerson.txt`, `etat/L5-diag_dump55exec_GI_GenerateAndApplyStrings.txt`. + +### Sous-lot L4-CX-B08 — `ParseIntoArray` au comportement UE 4.27 (ERR-027) — validé statiquement + +Tag d'entrée `L4-CX-B08-avant`. Sauvegarde : `D:\Game\LifePlay_Backup_5.5\L4-CX-B08\` (17 Blueprints ; 4 fichiers +source créés, retirés par `Restore.ps1` en cas de retour). + +| Étape | Résultat | Preuve (`etat/`) | +|---|---|---| +| Fonction de remplacement | `ULPStringCompat::ParseIntoArray` : transcription de l'algorithme 4.27 (chaîne vide → `[]` ; séparateur sensible à la casse), même signature que `UKismetStringLibrary::ParseIntoArray` | `Source/LifePlay/LPStringCompat.*` | +| Simulation | 114 nœuds, 17 Blueprints (GI 73, Person 10, Room 7, StreetMap_Static 4, 13 widgets) ; rien écrit | `L4-CX-B08_dryrun.txt` | +| Application | 114 rebranchés, 0 écart de liaison (contrôle intégré : liaisons et valeurs par défaut de chaque broche identiques avant/après), 0 échec | `L4-CX-B08_application.txt` | +| Contrôles | 0 broche orpheline ; `CompileAllBlueprints` 0 erreur, 95 avertissements (inchangé), 0 échec de chargement | résumés `L4-CX-B08_*` | + +| ID | Date | Lot | Fichiers | Raison | Retour arrière | +|---|---|---|---|---|---| +| CHG-029 | 2026-09-29 | L4-CX-B08 | `Source/LifePlay/LPStringCompat.h/.cpp`, `Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h/.cpp` (nouveaux) ; 17 Blueprints | `ParseIntoArray` UE5 renvoie `[""]` pour une chaîne vide (UE4 : `[]`) : `generatePerson()` sans argument chargeait les 141 presets | `Tools\Restore.ps1 -Lot L4-CX-B08` ou `git checkout L4-CX-B08-avant -- Content Source` | +| CHG-030 | 2026-09-29 | L5 | `Source/LifePlayEditorTools/LPDumpFunctionCommandlet.cpp` | option `-Exec` (broches d'exécution) | `Tools\Restore.ps1 -Lot L5-dumpexec` | diff --git a/Source/LifePlay/LPStringCompat.cpp b/Source/LifePlay/LPStringCompat.cpp new file mode 100644 index 0000000..7ea1562 --- /dev/null +++ b/Source/LifePlay/LPStringCompat.cpp @@ -0,0 +1,27 @@ +// Portage UE 5.5 - voir LPStringCompat.h + +#include "LPStringCompat.h" + +TArray ULPStringCompat::ParseIntoArray(const FString& SourceString, const FString& Delimiter, const bool CullEmptyStrings) +{ + // Transcription de FString::ParseIntoArray, UE 4.27 (String.cpp, lignes 940-963). + TArray OutArray; + const TCHAR* Start = *SourceString; + const int32 DelimLength = Delimiter.Len(); + if (Start && *Start != TEXT('\0') && DelimLength) + { + while (const TCHAR* At = FCString::Strstr(Start, *Delimiter)) + { + if (!CullEmptyStrings || At - Start) + { + OutArray.Emplace(UE_PTRDIFF_TO_INT32(At - Start), Start); + } + Start = At + DelimLength; + } + if (!CullEmptyStrings || *Start) + { + OutArray.Emplace(Start); + } + } + return OutArray; +} diff --git a/Source/LifePlay/LPStringCompat.h b/Source/LifePlay/LPStringCompat.h new file mode 100644 index 0000000..7aeb225 --- /dev/null +++ b/Source/LifePlay/LPStringCompat.h @@ -0,0 +1,32 @@ +// Portage UE 5.5 (2026.9 beta) - correctif CX-B08 (ERR-027). +// +// UE 5.x a reecrit FString::ParseIntoArray sur UE::String::ParseTokens. Deux comportements +// ont change par rapport a UE 4.27, sans aucun avertissement : +// 1. chaine source vide, CullEmpty=false : UE4 renvoie [] ; UE5 renvoie [""] ; +// 2. recherche du separateur : UE4 respecte la casse (FCString::Strstr) ; UE5 l'ignore. +// +// Le cas 1 casse generatePerson() sans argument : "()" donne Presets=[""], GeneratePerson +// charge alors les 141 presets (suffixe "" + ".lpcharacter") et finit sur un corps legacy +// jamais charge -> personnage invisible et devetu. +// +// ULPStringCompat::ParseIntoArray reproduit a l'identique l'algorithme 4.27 (Source 4.27, +// Runtime/Core/Private/Containers/String.cpp, FString::ParseIntoArray). Ses parametres et +// ses valeurs par defaut sont ceux de UKismetStringLibrary::ParseIntoArray, pour que la +// commandlet LPFixParseIntoArray puisse rebrancher les noeuds existants sans perdre de liaison. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "LPStringCompat.generated.h" + +UCLASS() +class LIFEPLAY_API ULPStringCompat : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** ParseIntoArray avec le comportement exact de UE 4.27 (voir en-tete du fichier). */ + UFUNCTION(BlueprintPure, Category = "Utilities|String", meta = (DisplayName = "Parse Into Array (UE4)")) + static TArray ParseIntoArray(const FString& SourceString, const FString& Delimiter = FString(TEXT(" ")), const bool CullEmptyStrings = true); +}; diff --git a/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp b/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp new file mode 100644 index 0000000..c2737f0 --- /dev/null +++ b/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp @@ -0,0 +1,210 @@ +// Portage UE 5.5 - voir LPFixParseIntoArrayCommandlet.h + +#include "LPFixParseIntoArrayCommandlet.h" + +#include "AssetRegistry/AssetData.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphPin.h" +#include "Engine/Blueprint.h" +#include "K2Node_CallFunction.h" +#include "Kismet/KismetStringLibrary.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "LPStringCompat.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "UObject/SavePackage.h" +#include "UObject/UObjectGlobals.h" + +DEFINE_LOG_CATEGORY_STATIC(LogLPFixParse, Log, All); + +namespace +{ + const FName ParseIntoArrayName(TEXT("ParseIntoArray")); + + /** Liaisons et valeur par defaut de chaque broche, par nom : l'empreinte a retrouver. */ + TMap Topology(const UEdGraphNode* Node) + { + TMap Out; + for (const UEdGraphPin* Pin : Node->Pins) + { + if (!Pin || Pin->bOrphanedPin) + { + continue; + } + TArray Links; + for (const UEdGraphPin* L : Pin->LinkedTo) + { + Links.Add(FString::Printf(TEXT("%s.%s"), + L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"), + L ? *L->PinName.ToString() : TEXT("?"))); + } + Links.Sort(); + Out.Add(Pin->PinName, FString::Printf(TEXT("[%s] defaut='%s'"), *FString::Join(Links, TEXT(", ")), *Pin->DefaultValue)); + } + return Out; + } +} + +ULPFixParseIntoArrayCommandlet::ULPFixParseIntoArrayCommandlet() +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; +} + +int32 ULPFixParseIntoArrayCommandlet::Main(const FString& Params) +{ + const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); + + UFunction* OldFunction = UKismetStringLibrary::StaticClass()->FindFunctionByName(ParseIntoArrayName); + UFunction* NewFunction = ULPStringCompat::StaticClass()->FindFunctionByName(ParseIntoArrayName); + if (!OldFunction || !NewFunction) + { + UE_LOG(LogLPFixParse, Error, TEXT("Fonction introuvable (ancienne=%s, nouvelle=%s)."), + OldFunction ? TEXT("ok") : TEXT("NULL"), NewFunction ? TEXT("ok") : TEXT("NULL")); + return 1; + } + + UE_LOG(LogLPFixParse, Warning, TEXT("=== KismetStringLibrary::ParseIntoArray -> LPStringCompat::ParseIntoArray%s ==="), + bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); + + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray BlueprintAssets; + AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); + + int32 NodesFound = 0; + int32 NodesFixed = 0; + int32 Unhandled = 0; + int32 BlueprintsModified = 0; + int32 SaveFailures = 0; + + for (const FAssetData& AssetData : BlueprintAssets) + { + if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) + { + continue; + } + UBlueprint* Blueprint = Cast(AssetData.GetAsset()); + if (!Blueprint) + { + continue; + } + + TArray Graphs; + Blueprint->GetAllGraphs(Graphs); + + int32 FixedHere = 0; + int32 FailedHere = 0; + + for (UEdGraph* Graph : Graphs) + { + if (!Graph) + { + continue; + } + TArray Snapshot = Graph->Nodes; + for (UEdGraphNode* GraphNode : Snapshot) + { + UK2Node_CallFunction* CallNode = Cast(GraphNode); + if (!CallNode || CallNode->GetTargetFunction() != OldFunction) + { + continue; + } + + ++NodesFound; + const TMap Before = Topology(CallNode); + UE_LOG(LogLPFixParse, Warning, TEXT(" %s :: %s -> %s | Delimiter %s | CullEmptyStrings %s"), + *Blueprint->GetName(), *Graph->GetName(), *CallNode->GetName(), + *Before.FindRef(TEXT("Delimiter")), *Before.FindRef(TEXT("CullEmptyStrings"))); + + if (bDryRun) + { + continue; + } + + CallNode->Modify(); + CallNode->SetFromFunction(NewFunction); + CallNode->ReconstructNode(); + + const TMap After = Topology(CallNode); + bool bSame = Before.Num() == After.Num(); + for (const TPair& It : Before) + { + const FString* A = After.Find(It.Key); + if (!A || *A != It.Value) + { + bSame = false; + UE_LOG(LogLPFixParse, Error, TEXT(" broche %s : avant %s | apres %s"), + *It.Key.ToString(), *It.Value, A ? **A : TEXT("(absente)")); + } + } + if (bSame && CallNode->GetTargetFunction() == NewFunction) + { + ++NodesFixed; + ++FixedHere; + } + else + { + ++Unhandled; + ++FailedHere; + } + } + } + + if (FailedHere > 0) + { + // Rien n'est sauvegarde : le paquet reste tel qu'il est sur disque. + UE_LOG(LogLPFixParse, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."), + *Blueprint->GetName(), FailedHere); + continue; + } + + if (FixedHere > 0 && !bDryRun) + { + ++BlueprintsModified; + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + UPackage* Package = Blueprint->GetOutermost(); + FString Filename; + if (!FPackageName::TryConvertLongPackageNameToFilename( + Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) + { + ++SaveFailures; + UE_LOG(LogLPFixParse, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName()); + continue; + } + + ResetLoaders(Package); + + FSavePackageArgs SaveArgs; + SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; + SaveArgs.SaveFlags = SAVE_None; + SaveArgs.bWarnOfLongFilename = true; + // GWarn et non GError : en commandlet, GError rend toute erreur de save fatale. + SaveArgs.Error = GWarn; + + if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) + { + UE_LOG(LogLPFixParse, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); + } + else + { + ++SaveFailures; + UE_LOG(LogLPFixParse, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); + } + } + } + + UE_LOG(LogLPFixParse, Warning, + TEXT("=== Termine : %d noeud(s) trouve(s), %d rebranche(s), %d Blueprint(s) modifie(s), %d en ecart, %d echec(s) de sauvegarde. ==="), + NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures); + + return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0; +} diff --git a/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h b/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h new file mode 100644 index 0000000..6f2bf39 --- /dev/null +++ b/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h @@ -0,0 +1,28 @@ +// Portage UE 5.5 - correctif CX-B08 (ERR-027). Voir Source/LifePlay/LPStringCompat.h. +// +// Rebranche chaque noeud KismetStringLibrary::ParseIntoArray des Blueprints de /Game vers +// LPStringCompat::ParseIntoArray (comportement UE 4.27). Memes noms de broches : le +// rebranchement se limite a un changement de reference de fonction suivi d'un ReconstructNode. +// +// Controle integre : avant modification, les liaisons et valeurs par defaut de chaque broche +// sont relevees ; apres reconstruction, elles doivent etre retrouvees a l'identique. Tout +// ecart compte le noeud comme non traite et le Blueprint n'est pas sauvegarde. +// +// Usage : +// UnrealEditor-Cmd.exe -run=LPFixParseIntoArray [-DryRun] + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/Commandlet.h" +#include "LPFixParseIntoArrayCommandlet.generated.h" + +UCLASS() +class ULPFixParseIntoArrayCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + ULPFixParseIntoArrayCommandlet(); + virtual int32 Main(const FString& Params) override; +};