LifePlay-Revival/Docs/Migration/etat/ST/STC/STC-T3-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

23 KiB
Raw Blame History

Fiche STC-T3 — Niveau N3 — Modèle prévu lp-impl | utilisé claude-opus-5-5 — Agent lp-impl — Skills lp-ue-compat, ue-cpp-build, lp-build, sk-protocol

STC-T3-IMPL : C++ ULPUserSettings (GameUserSettings.ini) et style des sliders de Settings (2026-10-10)

Copie D:\Game\LifePlay-master 5.8, branche ue5.8, HEAD 20620f6. SPEC : STC-SPEC.md § 0-2, 7 (lue en entier). Non compilé (fiche : BUILD par lp-builder, éditeur ouvert). Aucun asset, config, Build.cs ni git touché.

1. Fichiers modifiés (chemins, lignes)

Fichier État Lignes (wc -l) Fins de ligne (git ls-files --eol / -o --eol) MD5
D:\Game\LifePlay-master 5.8\Source\LifePlay\LPUserSettings.h neuf 59 w/lf (comme le modèle LPModSettingsLibrary.h/.cpp, i/lf w/lf) 7d67c3f755930bcf1c0fd4c65aced897
D:\Game\LifePlay-master 5.8\Source\LifePlay\LPUserSettings.cpp neuf 115 w/lf ba74f9e3e6dbaa4a02579be89ea2e037
D:\Game\LifePlay-master 5.8\Source\LifePlay\LPSettingsSubsystem.cpp modifié, +52 / -0 (git diff --stat) 835 → 887 i/crlf w/crlf (inchangé, CRLF gardé par Edit) 2fe96f77… → ede28e6d9aeff9a27f1adb988c03ae36
  • LPSettingsSubsystem.h non modifié (MD5 0f98c055… = instantané). LifePlay.Build.cs non modifié : Slate, SlateCore, UMG déjà en privé (LifePlay.Build.cs:38), CoreUObject/Engine en public (:17) ; aucune dépendance manquante (estimé : toutes les API utilisées sont dans ces modules).
  • Dossier Source/LifePlay : 18 fichiers CRLF, 35 LF (git ls-files --eol -- Source/LifePlay) ; les neufs suivent leur modèle (LF).
  • Aucun caractère hors ASCII dans les deux fichiers neufs (Grep [^\x00-\x7F] : 0 résultat).
  • Emplacements dans LPSettingsSubsystem.cpp (après modification) : include Components/Slider.h l. 19 ; CollectSliders l. 281-303 ; couleurs l. 346-351 ; collecte l. 355 et 360 ; boucle de style l. 403-420 (SetWidgetStyle l. 415, SetSliderBarColor l. 417, SetSliderHandleColor l. 418, Grep relu).

2. Écarts à la SPEC (obligatoire)

  • E1 (style, § 2.3) : les couleurs sont portées par la teinte des pinceaux du style (NormalBarImage 0.06/0.06/0.06/0.95, HoveredBarImage 0.12/0.12/0.12/0.98, NormalThumbImage 0.2/0.45/0.9/1, HoveredThumbImage 0.3/0.55/1/1) et SetSliderBarColor / SetSliderHandleColor reçoivent blanc (multiplicateur neutre). Motif : SSlider::OnPaint dessine teinte du pinceau × couleur (SSlider.cpp:125, 137) ; mettre les couleurs dans les pinceaux garde un état survolé distinct (GetBarImage/GetThumbImage, SSlider.cpp:483-513). Les teintes de survol (0.12 et 0.3/0.55/1) ne sont pas dans la SPEC (reprises de SpinBgHover et CheckedHover). Base = style propre du slider (GetWidgetStyle), pas FCoreStyle comme les SpinBox : pinceaux, BarThickness (2 px par défaut) et taille de poignée (8×14) gardés. Seuls appels d'écriture : les trois permis par la SPEC ; aucun SetValue, ni SetIsEnabled/SetRenderOpacity.
  • E2 (portée du style, § 2.3) : appliqué, comme écrit, à tous les USlider de SettingsScrollBox : donc aussi aux 5 lignes LPColor* (LPColorGradeSubsystem.cpp:298-302), aux sliders LPVideo* (LPVideoQualitySubsystem.cpp:392) et à tout slider Blueprint de la liste. Effet visible au-delà du slider caméra : à confirmer par l'orchestrateur (une restriction au nom SliderCameraSpeed serait un changement d'une ligne).
  • E3 (propriété GI absente) : interprétation de « avertissement, rien d'autre ». ApplyCameraSpeedAtStartup : avertissement puis retour, ini non écrit dans les deux branches (la reprise du premier lancement sera retentée). SetCameraSpeed : avertissement pour GI, ini quand même mis à jour (l'ini fait foi, D4). GameInstance nul = même traitement.
  • E4 : SetCameraSpeed écrit une ligne Display à chaque écriture de l'ini (Camera : vitesse N enregistree dans GameUserSettings.ini) ; non demandé, utile à V5 ; au plus une ligne par cran réellement changé.
  • E5 : formule de la SPEC gardée telle quelle, sans garde NaN : FMath::Clamp = Max(Min(X, Max), Min) (UnrealMathUtility.h:592-595), donc NaN → 7 (estimé par lecture du code, non essayé).
  • E6 : broche V gardée (nom de la SPEC) pour ClampCameraSpeed et SetCameraSpeed.
  • Aucun autre écart : signatures, catégorie LifePlay|Settings, UCLASS(config = GameUserSettings), valeurs par défaut (3, faux), SaveConfig() seulement si valeur changée ou bCameraSpeedSet faux, lecture/écriture GI par réflexion FDoubleProperty puis FFloatProperty.

3. Instantané (lot, manifeste)

  • Lot STC, manifeste D:\Game\LifePlay_Backup_5.5\STC\manifest.csv (fait par l'orchestrateur, 2026-10-10T09:11:26) : LPUserSettings.h et .cpp Existed=False ; LPSettingsSubsystem.cpp MD5 2FE96F776A0F3DFDA803F6E0EB1BC73B et .h 0F98C05568AE789C5C0B78F6EC580503, identiques aux fichiers avant modification (md5sum, relu avant la première écriture).
  • Retour arrière (proposé, non fait) : Tools\Restore.ps1 -Lot STC (déplace les deux neufs, restaure le .cpp).

4. Ce qui n'a pas été fait

  • Compilation (BUILD par lp-builder, éditeur fermé) ; aucun build 5.5 de contrôle (le lot vit dans la copie 5.8).
  • Blueprints MainMenu (§ 3) et GI (§ 4), CHG, ini, GameSettings.sav : hors fiche.
  • Aucun commit, aucun git add, aucune écriture dans Docs/ de la copie 5.8, aucun script.

5. Points à confirmer en VAL

  • BUILD 5.8 : code 0 ; LPUserSettings.gen.cpp produit par UHT ; LPUserSettings.cpp et LPSettingsSubsystem.cpp compilés ; 0 avertissement nouveau (règles V7 : ombrage, code inatteignable).
  • Unity build : espace de noms LPUserSettingsGi (nommé, unique : Grep LPUserSettings sur Source, Plugins et addon 5.8 avant écriture = 0) sans C2374.
  • Palette Blueprint : 4 nœuds sous LifePlay|Settings avec les broches de la table « Signatures » (Constats).
  • V8 : journal LogLifePlay Display « Camera : vitesse N reprise de GI (GameSettings.sav : X), ecrite dans GameUserSettings.ini (premier lancement) » et section [/Script/LifePlay.LPUserSettings] avec CameraSpeed=… et bCameraSpeedSet=True.
  • V8 (risque) : valeur de CameraSpeed dans GameSettings.sav avant migration ; si elle vaut 0.2 (cause (c)), la reprise donne 1, pas 3 (Round(0.2)=0 puis borne 1) ; attendu SPEC V8 = 3.
  • V6 : au relancement, ligne « Camera : vitesse 5 lue dans GameUserSettings.ini (ini 5.00), recopiee dans GI (avant X) » et GI.CameraSpeed = 5.
  • V5 : une ligne « enregistree dans GameUserSettings.ini » par cran changé, aucune quand la valeur ne change pas ; fichier écrit sur disque (SaveConfig → Flush, Obj.cpp:3942-3944).
  • V9 : seule la section LPUserSettings ajoutée dans GameUserSettings.ini.
  • Style (E1, E2) : slider caméra lisible sur fond clair (barre 2 px, poignée 8×14 inchangées) ; aspect des sliders LPColor* / LPVideo* / Blueprint acceptable ; aucune valeur de slider modifiée à l'ouverture du menu.
  • Cases et SpinBox : rendu inchangé (aucune ligne de leur traitement modifiée, diff relu).

6. Fichiers à commiter

Dépôt jeu D:\Game\LifePlay-master 5.8, branche ue5.8 : A Source/LifePlay/LPUserSettings.h A Source/LifePlay/LPUserSettings.cpp M Source/LifePlay/LPSettingsSubsystem.cpp

Tours : 56 / 60

Constats

Signatures telles qu'un Blueprint les verra (estimé : affichage par FName::NameToDisplayString, à confirmer en VAL)

Cible : LPUser Settings Library (classe ULPUserSettingsLibrary, statiques, aucune broche Target). Catégorie LifePlay|Settings.

Nœud (titre) C++ Type Broches d'entrée (nom interne → affiché) Broches de sortie
Clamp Camera Speed static float ClampCameraSpeed(float V) BlueprintPure V → « V » (Float simple précision) ReturnValue → « Return Value » (Float)
Apply Camera Speed At Startup static void ApplyCameraSpeedAtStartup(UObject* GameInstance) BlueprintCallable execute ; GameInstance → « Game Instance » (Object Reference) then
Set Camera Speed static void SetCameraSpeed(UObject* GameInstance, float V) BlueprintCallable execute ; GameInstance → « Game Instance » ; V → « V » (Float) then
Get Camera Speed static float GetCameraSpeed() BlueprintPure — ReturnValue → « Return Value » (Float)
Pas de DefaultToSelf : la broche GameInstance doit être reliée (self dans GI, Get Game Instance dans MainMenu).
Classe de configuration : ULPUserSettings (UCLASS(config = GameUserSettings), UPROPERTY(config) float CameraSpeed = 3.f,
UPROPERTY(config) bool bCameraSpeedSet = false), non visible des Blueprints (pas de spécificateur Blueprint, conforme SPEC 2.1).

Choix faits

  • Accès à l'ini : GetMutableDefault<ULPUserSettings>() (objet par défaut chargé de l'ini à sa création), écriture SaveConfig() sans argument : fichier = nom de config de la classe (Obj.cpp:3697, GetConfigFilename Obj.cpp:2332-2338, UClass::GetConfigName Class.cpp:7144-7149 : GameUserSettings est un nom de config connu), puis Flush sur disque (Obj.cpp:3942-3944).
  • Réflexion GI : FindPropertyByName("CameraSpeed"), CastField<FDoubleProperty> puis CastField<FFloatProperty> (UnrealType.h:2507, 2547 ; CastField const Field.h:1133) ; Get/SetPropertyValue_InContainer const (UnrealType.h:1663, 1673). Tout autre type ou absence : Warning « pas de propriete reelle CameraSpeed dans , GI non touche ».
  • ApplyCameraSpeedAtStartup : une ligne Display par appel réussi (source ini avec valeur ini et valeur GI d'avant, ou reprise depuis GI avec valeur du sav) ; SaveConfig seulement dans la branche premier lancement.
  • SetCameraSpeed : GI toujours écrit (valeur bornée) ; ini et SaveConfig seulement si !bCameraSpeedSet || CameraSpeed != V (comparaison exacte : entiers 1-7 exacts en float).
  • GetCameraSpeed : bCameraSpeedSet ? ClampCameraSpeed(CameraSpeed) : 3.
  • Helpers dans l'espace de noms nommé LPUserSettingsGi (piège unity C2374, lp-build § 8) ; FName en static local (comme LPModSettingsLibrary.cpp:13).
  • Compatibilité 5.5 (estimé, non compilé) : API du slider présentes en 5.5 (UE_5.5/.../Components/Slider.h:147, 150, 184, 191) ; aucune garde de version nécessaire ; aucun correctif UE4 (LPStringCompat, LPMeshCompat, LPNavigationHelpers…) touché.

API moteur 5.8 lues (établi)

  • UE_5.8/Engine/Source/Runtime/UMG/Public/Components/Slider.h:151 GetWidgetStyle, :154 SetWidgetStyle, :188 SetSliderBarColor(FLinearColor), :195 SetSliderHandleColor(FLinearColor) ; accès direct aux membres déprécié 5.1 (:51, 61, 66).
  • UMG/Private/Components/Slider.cpp:210-217, 284-305 : ces setters ne touchent pas Value ; :30 style d'exécution par défaut = FDefaultStyleCache::GetRuntime().GetSliderStyle() (SlateCore/Private/Styling/DefaultStyleCache.cpp:60 → UMGCoreStyle.cpp:723-731 : barre FSlateColorBrush blanche, poignée Common/Button 8×14, épaisseur 2).
  • SlateCore/Public/Styling/SlateTypes.h:1339-1386 : FSliderStyle (NormalBarImage, HoveredBarImage, DisabledBarImage, NormalThumbImage, HoveredThumbImage, DisabledThumbImage, BarThickness).
  • Slate/Private/Widgets/Input/SSlider.cpp:119-138 (peinture : teinte × couleur × teinte du widget), :483-513 (pinceau survolé).
  • CoreUObject/Public/UObject/Object.h:1283 SaveConfig(uint64 RequiredPropertyFlags = CPF_Config, const TCHAR* Filename = nullptr, …).
  • Core/Public/GenericPlatform/GenericPlatformMath.h:332 RoundToFloat(float) ; Core/Public/Math/UnrealMathUtility.h:592 Clamp. Aucune API supposée par la SPEC absente en 5.8 (arrêt rubrique 7 non déclenché).

Diff complet

Source/LifePlay/LPSettingsSubsystem.cpp (git diff, CR retirés à l'affichage) :

@@ -16,6 +16,7 @@
 #include "Components/OverlaySlot.h"
 #include "Components/ScrollBox.h"
 #include "Components/SizeBox.h"
+#include "Components/Slider.h"
 #include "Components/SpinBox.h"
 #include "Components/TextBlock.h"
 #include "Components/PanelWidget.h"
@@ -277,6 +278,30 @@ namespace LPSettingsUi
 		}
 	}
 
+	void CollectSliders(UWidget* Widget, TArray<USlider*>& Out)
+	{
+		if (!Widget)
+		{
+			return;
+		}
+		if (USlider* Slider = Cast<USlider>(Widget))
+		{
+			Out.Add(Slider);
+			return;
+		}
+		if (UUserWidget* User = Cast<UUserWidget>(Widget))
+		{
+			CollectSliders(User->GetRootWidget(), Out);
+		}
+		if (UPanelWidget* Panel = Cast<UPanelWidget>(Widget))
+		{
+			for (int32 Index = 0; Index < Panel->GetChildrenCount(); ++Index)
+			{
+				CollectSliders(Panel->GetChildAt(Index), Out);
+			}
+		}
+	}
+
 	void TintBrush(FSlateBrush& Brush, const FLinearColor& Tint)
 	{
 		Brush.TintColor = FSlateColor(Tint);
@@ -318,12 +343,21 @@ namespace LPSettingsUi
 		TintBrush(SpinStyle.ActiveFillBrush, FLinearColor(0.2f, 0.45f, 0.9f, 0.85f));
 		SpinStyle.ForegroundColor = FSlateColor(FLinearColor(1.f, 1.f, 1.f, 1.f));
 
+		// Lot STC : sliders (vitesse de camera, lignes LPColor* et LPVideo*). Barre sombre comme le fond des spinboxes,
+		// poignee bleue comme leur remplissage. Style et couleurs seulement : aucune valeur ecrite (pas de SetValue).
+		const FLinearColor SliderBar(0.06f, 0.06f, 0.06f, 0.95f);
+		const FLinearColor SliderBarHover(0.12f, 0.12f, 0.12f, 0.98f);
+		const FLinearColor SliderHandle(0.2f, 0.45f, 0.9f, 1.f);
+		const FLinearColor SliderHandleHover(0.3f, 0.55f, 1.f, 1.f);
+
 		TArray<UCheckBox*> Boxes;
 		TArray<USpinBox*> Spins;
+		TArray<USlider*> Sliders;
 		for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index)
 		{
 			CollectCheckBoxes(Scroll->GetChildAt(Index), Boxes);
 			CollectSpinBoxes(Scroll->GetChildAt(Index), Spins);
+			CollectSliders(Scroll->GetChildAt(Index), Sliders);
 		}
 
 		for (UCheckBox* Box : Boxes)
@@ -366,6 +400,24 @@ namespace LPSettingsUi
 			Spin->SetFont(Font);
 		}
 
+		for (USlider* Slider : Sliders)
+		{
+			if (!Slider)
+			{
+				continue;
+			}
+			// Style propre du slider (pinceaux et epaisseur gardes), seules les teintes changent.
+			FSliderStyle SliderStyle = Slider->GetWidgetStyle();
+			TintBrush(SliderStyle.NormalBarImage, SliderBar);
+			TintBrush(SliderStyle.HoveredBarImage, SliderBarHover);
+			TintBrush(SliderStyle.NormalThumbImage, SliderHandle);
+			TintBrush(SliderStyle.HoveredThumbImage, SliderHandleHover);
+			Slider->SetWidgetStyle(SliderStyle);
+			// SSlider::OnPaint multiplie la teinte du pinceau par ces couleurs : blanc = teintes ci-dessus telles quelles.
+			Slider->SetSliderBarColor(FLinearColor::White);
+			Slider->SetSliderHandleColor(FLinearColor::White);
+		}
+
 		if (UWidget* ShowFPS = [&]() -> UWidget*
 			{
 				for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index)

Source/LifePlay/LPUserSettings.h (fichier neuf, 59 lignes, contenu intégral) :

// Lot STC - vitesse de camera adossee a GameUserSettings.ini (STC-SPEC, partie 2).
//
// La valeur de reference de la vitesse de camera est la section [/Script/LifePlay.LPUserSettings] de
// GameUserSettings.ini (PIE : Saved/Config/WindowsEditor/, jeu : Saved/Config/Windows/). GI.CameraSpeed n'en est
// qu'une recopie : ApplyCameraSpeedAtStartup la remplit au demarrage (apres GI.LoadSettings), SetCameraSpeed la met a
// jour quand le joueur bouge le slider du menu Settings. L'ini n'est ecrit que si la valeur change, ou une fois au
// premier lancement (bCameraSpeedSet faux) : la valeur de GI, issue de GameSettings.sav, est alors reprise et bornee.
// GameSettings.sav garde son champ CameraSpeed, recopie de GI par GI.SaveSettings.
// Valeurs permises : entiers de 1 a 7 (slider a pas de 1), 3 par defaut.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "UObject/Object.h"
#include "LPUserSettings.generated.h"

/** Reglages du joueur propres a LifePlay, enregistres dans GameUserSettings.ini (objet par defaut seulement). */
UCLASS(config = GameUserSettings)
class LIFEPLAY_API ULPUserSettings : public UObject
{
	GENERATED_BODY()

public:
	/** Vitesse de camera choisie dans le menu Settings (1 a 7). */
	UPROPERTY(config)
	float CameraSpeed = 3.f;

	/** Faux tant que l'entree n'a jamais ete ecrite (premier lancement : reprise de GI.CameraSpeed). */
	UPROPERTY(config)
	bool bCameraSpeedSet = false;
};

UCLASS()
class LIFEPLAY_API ULPUserSettingsLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	/** Arrondit a l'entier le plus proche et borne entre 1 et 7. */
	UFUNCTION(BlueprintPure, Category = "LifePlay|Settings")
	static float ClampCameraSpeed(float V);

	/**
	 * A appeler au demarrage, apres GI.LoadSettings : recopie dans GI.CameraSpeed la valeur de GameUserSettings.ini.
	 * Premier lancement (entree jamais ecrite) : reprend GI.CameraSpeed (lu dans GameSettings.sav), la borne, l'ecrit
	 * dans GI et dans l'ini.
	 */
	UFUNCTION(BlueprintCallable, Category = "LifePlay|Settings")
	static void ApplyCameraSpeedAtStartup(UObject* GameInstance);

	/** Slider du menu Settings : borne V, l'ecrit dans GI.CameraSpeed et, si elle change, dans GameUserSettings.ini. */
	UFUNCTION(BlueprintCallable, Category = "LifePlay|Settings")
	static void SetCameraSpeed(UObject* GameInstance, float V);

	/** Vitesse de camera enregistree dans GameUserSettings.ini, bornee (3 si jamais ecrite). */
	UFUNCTION(BlueprintPure, Category = "LifePlay|Settings")
	static float GetCameraSpeed();
};

Source/LifePlay/LPUserSettings.cpp (fichier neuf, 115 lignes, contenu intégral) :

// Lot STC - voir LPUserSettings.h

#include "LPUserSettings.h"

#include "LifePlay.h"
#include "UObject/UnrealType.h"

namespace LPUserSettingsGi
{
	constexpr float DefaultCameraSpeed = 3.f;

	// GI.CameraSpeed est une variable Blueprint : reel double en 5.x (FDoubleProperty), float accepte aussi.
	// Propriete absente ou d'un autre type : avertissement, GI n'est pas touche.
	FProperty* FindCameraSpeed(UObject* GameInstance, const TCHAR* Caller)
	{
		static const FName CameraSpeedName(TEXT("CameraSpeed"));
		if (!GameInstance)
		{
			UE_LOG(LogLifePlay, Warning, TEXT("Camera : %s sans GameInstance, GI.CameraSpeed non touche"), Caller);
			return nullptr;
		}
		FProperty* Prop = GameInstance->GetClass()->FindPropertyByName(CameraSpeedName);
		if (!CastField<FDoubleProperty>(Prop) && !CastField<FFloatProperty>(Prop))
		{
			UE_LOG(LogLifePlay, Warning, TEXT("Camera : %s, pas de propriete reelle CameraSpeed dans %s, GI non touche"),
				Caller, *GameInstance->GetClass()->GetName());
			return nullptr;
		}
		return Prop;
	}

	float ReadValue(const FProperty* Prop, const UObject* GameInstance)
	{
		if (const FDoubleProperty* AsDouble = CastField<FDoubleProperty>(Prop))
		{
			return static_cast<float>(AsDouble->GetPropertyValue_InContainer(GameInstance));
		}
		if (const FFloatProperty* AsFloat = CastField<FFloatProperty>(Prop))
		{
			return AsFloat->GetPropertyValue_InContainer(GameInstance);
		}
		return DefaultCameraSpeed;
	}

	void WriteValue(const FProperty* Prop, UObject* GameInstance, float Value)
	{
		if (const FDoubleProperty* AsDouble = CastField<FDoubleProperty>(Prop))
		{
			AsDouble->SetPropertyValue_InContainer(GameInstance, static_cast<double>(Value));
		}
		else if (const FFloatProperty* AsFloat = CastField<FFloatProperty>(Prop))
		{
			AsFloat->SetPropertyValue_InContainer(GameInstance, Value);
		}
	}
}

float ULPUserSettingsLibrary::ClampCameraSpeed(float V)
{
	return FMath::Clamp(FMath::RoundToFloat(V), 1.f, 7.f);
}

void ULPUserSettingsLibrary::ApplyCameraSpeedAtStartup(UObject* GameInstance)
{
	ULPUserSettings* Settings = GetMutableDefault<ULPUserSettings>();
	FProperty* Prop = LPUserSettingsGi::FindCameraSpeed(GameInstance, TEXT("ApplyCameraSpeedAtStartup"));
	if (!Prop)
	{
		// Avertissement deja ecrit. Ini inchange : au premier lancement, la reprise depuis GI sera retentee.
		return;
	}
	if (Settings->bCameraSpeedSet)
	{
		const float Value = ClampCameraSpeed(Settings->CameraSpeed);
		const float Before = LPUserSettingsGi::ReadValue(Prop, GameInstance);
		LPUserSettingsGi::WriteValue(Prop, GameInstance, Value);
		UE_LOG(LogLifePlay, Display, TEXT("Camera : vitesse %.0f lue dans GameUserSettings.ini (ini %.2f), recopiee dans GI (avant %.2f)"),
			Value, Settings->CameraSpeed, Before);
		return;
	}
	// Premier lancement : entree jamais ecrite. GI.CameraSpeed vient de GameSettings.sav (GI.LoadSettings).
	const float FromGi = LPUserSettingsGi::ReadValue(Prop, GameInstance);
	const float Value = ClampCameraSpeed(FromGi);
	LPUserSettingsGi::WriteValue(Prop, GameInstance, Value);
	Settings->CameraSpeed = Value;
	Settings->bCameraSpeedSet = true;
	Settings->SaveConfig();
	UE_LOG(LogLifePlay, Display, TEXT("Camera : vitesse %.0f reprise de GI (GameSettings.sav : %.2f), ecrite dans GameUserSettings.ini (premier lancement)"),
		Value, FromGi);
}

void ULPUserSettingsLibrary::SetCameraSpeed(UObject* GameInstance, float V)
{
	const float Value = ClampCameraSpeed(V);
	if (FProperty* Prop = LPUserSettingsGi::FindCameraSpeed(GameInstance, TEXT("SetCameraSpeed")))
	{
		LPUserSettingsGi::WriteValue(Prop, GameInstance, Value);
	}
	// L'ini fait foi : ecrit seulement si la valeur change, ou si l'entree n'a jamais ete ecrite.
	ULPUserSettings* Settings = GetMutableDefault<ULPUserSettings>();
	if (Settings->bCameraSpeedSet && Settings->CameraSpeed == Value)
	{
		return;
	}
	Settings->CameraSpeed = Value;
	Settings->bCameraSpeedSet = true;
	Settings->SaveConfig();
	UE_LOG(LogLifePlay, Display, TEXT("Camera : vitesse %.0f enregistree dans GameUserSettings.ini"), Value);
}

float ULPUserSettingsLibrary::GetCameraSpeed()
{
	const ULPUserSettings* Settings = GetDefault<ULPUserSettings>();
	return Settings->bCameraSpeedSet ? ClampCameraSpeed(Settings->CameraSpeed) : LPUserSettingsGi::DefaultCameraSpeed;
}

Commandes de contrôle rejouables

  • git -C "D:/Game/LifePlay-master 5.8" status --short → M Source/LifePlay/LPSettingsSubsystem.cpp, ?? Source/LifePlay/LPUserSettings.cpp, ?? Source/LifePlay/LPUserSettings.h (+ ?? Docs/Migration/32_Organisation_Agents.md, préexistant, non touché).
  • git -C "D:/Game/LifePlay-master 5.8" ls-files --eol -- Source/LifePlay/LPSettingsSubsystem.cpp → i/crlf w/crlf ; git … ls-files -o --eol -- Source/LifePlay/ → w/lf pour les deux neufs.

Addendum orchestrateur (2026-10-10, après remise du livrable)

Écart E2 tranché : le style ne s'applique qu'au slider SliderCameraSpeed (les sliders LPColor* / LPVideo* gardent leur aspect). Code actuel de la boucle : if (!Slider || Slider->GetFName() != TEXT("SliderCameraSpeed")) { continue; } et commentaire « slider de vitesse de camera seulement ». MD5 actuel de LPSettingsSubsystem.cpp : b72671af… (le MD5 ede28e6d… cité plus haut est celui de la version remise par lp-impl). Le diff de ce livrable décrit la version antérieure ; la version qui fait foi est celle relue en STC-VAL (contrôle a).