LifePlay-Revival/Docs/Migration/etat/REL/ALPHA/REL-ALPHA-V2-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

12 KiB

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

REL-ALPHA-V2-IMPL : suffixe de lettres de la dernière composante dans ParseVersion (2026-10-14)

SPEC appliquée : REL-ALPHA-V-SPEC.md § 2 (lue en entier). Aucune compilation (fiche, rubrique 3).

1. Fichiers modifiés (chemins, lignes)

  • D:\Game\LifePlay-master 5.8\Source\LifePlay\LPModGuard.cpp, fonction LPModGuardImpl::ParseVersion, désormais l. 52-92 (avant : l. 52-74) :
    • l. 52-54 : commentaire de la fonction (lot REL-ALPHA, décision « Lettre acceptée » du 2026-10-14 écrite en ASCII, exemple "2026.10.2.1a" -> {2026, 10, 2, 1}) ;
    • l. 60-75 : bloc neuf, retrait des lettres ASCII de fin de la dernière composante ('a'..'z', 'A'..'Z', tests de plages explicites), puis LeftInline ;
    • l. 76-91 : boucle des contrôles existants, octet pour octet inchangée (non vide, 9 caractères au plus, FChar::IsDigit, Atoi64, Out.Num() > 0).
  • LPModGuard.h : non modifié (voir § 2, E2).
  • Appelants inchangés, décalés de 18 lignes : l. 291 (GGameVersion), l. 292 (MinGameVersion), l. 385 (MODULE_GAME_VERSION) ; CompareVersions l. 95, non touché.

Diff complet (commande git -C "D:/Game/LifePlay-master 5.8" diff -- Source/LifePlay/LPModGuard.cpp Source/LifePlay/LPModGuard.h, sortie collée telle quelle) :

diff --git a/Source/LifePlay/LPModGuard.cpp b/Source/LifePlay/LPModGuard.cpp
index ca31179..e5721c8 100644
--- a/Source/LifePlay/LPModGuard.cpp
+++ b/Source/LifePlay/LPModGuard.cpp
@@ -49,12 +49,30 @@ namespace LPModGuardImpl
 		FString Fr;
 	};
 
-	/** "2026.10.2.0" -> {2026, 10, 2, 0} ; faux si une composante n'est pas un entier (comparaison numerique, pas lexicale). */
+	/** "2026.10.2.0" -> {2026, 10, 2, 0} ; faux si une composante n'est pas un entier (comparaison numerique, pas lexicale).
+	 *  Lot REL-ALPHA (decision de l'utilisateur "Lettre acceptee", 2026-10-14) : la derniere composante peut finir par des
+	 *  lettres ASCII a-z / A-Z, retirees avant les controles et ignorees a la comparaison : "2026.10.2.1a" -> {2026, 10, 2, 1}. */
 	bool ParseVersion(const FString& Text, TArray<int64>& Out)
 	{
 		Out.Reset();
 		TArray<FString> Parts;
 		Text.TrimStartAndEnd().ParseIntoArray(Parts, TEXT("."), false);
+		// Lettres de fin de la derniere composante seulement, par plages explicites (aucune lettre non ASCII retiree).
+		if (Parts.Num() > 0)
+		{
+			FString& LastPart = Parts.Last();
+			int32 Keep = LastPart.Len();
+			while (Keep > 0)
+			{
+				const TCHAR C = LastPart[Keep - 1];
+				if (!((C >= TEXT('a') && C <= TEXT('z')) || (C >= TEXT('A') && C <= TEXT('Z'))))
+				{
+					break;
+				}
+				--Keep;
+			}
+			LastPart.LeftInline(Keep);
+		}
 		for (const FString& Part : Parts)
 		{
 			if (Part.IsEmpty() || Part.Len() > 9)

git diff --stat : Source/LifePlay/LPModGuard.cpp | 20 +++++++++++++++++++- / 1 file changed, 19 insertions(+), 1 deletion(-) (aucune ligne pour LPModGuard.h).

MD5, fins de ligne, ASCII (commande md5sum, git ls-files --eol, puis cat <cpp> | tr -cd '\r' | wc -c et cat <cpp> | LC_ALL=C tr -d '\000-\177' | wc -c) :

Fichier MD5 avant (manifeste D:\Game\LifePlay_Backup_5.5\REL-ALPHA\manifest.csv l. 2-3 ; md5sum avant Edit identique) MD5 après --eol après CR octets non ASCII
LPModGuard.cpp 239c49d0fc1e7d04dfdfdd51a930f2c5 54aac4d9ebc5db602a57274db4049c8e i/lf w/lf attr/-text 0 0
LPModGuard.h c09543bed1f8cf8311f3b058c2e40ce1 c09543bed1f8cf8311f3b058c2e40ce1 (inchangé) i/lf w/lf attr/-text sans objet (inchangé) sans objet (inchangé)

Sorties brutes : 54aac4d9ebc5db602a57274db4049c8e *Source/LifePlay/LPModGuard.cpp, c09543bed1f8cf8311f3b058c2e40ce1 *Source/LifePlay/LPModGuard.h, puis 0 (CR) et 0 (non ASCII). Avant l'Edit, git status --short des deux fichiers : vide (copie de travail = HEAD = instantané).

Contrôles du texte (Grep, motif IsAlpha|UE_LOG sur LPModGuard.cpp, mode count) : Found 10 total occurrences across 1 file = les 10 UE_LOG d'avant (l. 210, 214, 260, 267, 277, 362, 420, 425, 430, 503 de l'ancien fichier), 0 IsAlpha ; le diff n'ajoute ni UE_LOG ni message.

2. Écarts à la SPEC (obligatoire)

  • E1 (libellé de la SPEC, comportement conforme) : ligne « chaîne vide | illisible (0 composante) » du tableau § 2. Dans le moteur 5.8, ParseIntoArray("", ".", false) rend 1 composante vide, pas 0 : String.cpp.inl:1361-1367 (délimiteur non vide, bInCullEmpty = false donc pas de SkipEmpty) → ParseTokens.cpp:290-292 → ParseTokens1Delim1Char, boucle quittée aussitôt (:55-57) puis visite du jeton vide :86, gardé car SkipEmpty absent (:32). Le résultat reste « illisible », par Part.IsEmpty() (l. 78) et non par Out.Num() > 0 (l. 91). Même chemin qu'avant le changement.
  • E2 (commentaire LPModGuard.h:19 non touché) : son texte (« MODULE_GAME_VERSION du .lpmod >= MinGameVersion (comparaison numerique composante par composante) ») reste vrai : la comparaison est toujours numérique, composante par composante, le suffixe étant retiré avant. La fiche dit « seulement si son texte devient faux » ; la SPEC § 2 évoque une demi-ligne possible. Non ajoutée ; texte proposé si l'orchestrateur la veut : (lettres ASCII de fin de la derniere composante ignorees, lot REL-ALPHA).
  • E3 (forme) : la décision est citée "Lettre acceptee" sans accent (règle ASCII des sources, fiche rubrique 2).
  • E4 (forme, conforme à la SPEC) : le retrait est fait sur Parts.Last() avant la boucle, au lieu d'un test d'indice dans la boucle ; la boucle des contrôles existants reste identique au caractère près (diff : aucune ligne - ni + dans la boucle).
  • E5 (écart à la fiche, rubrique 3, de ma part) : une commande shell de vérification contenait un fragment parasite LC_ALL=C tr -d '\000-\177' -- 2>/dev/null; (redirection 2> vers /dev/null, sans fichier écrit, sans effet sur la mesure, qui vient de la commande suivante du même appel). Le crochet ne l'a pas refusée. Contraire à la lettre de « pas de > dans les commandes shell » : signalé, pas répété.

3. Instantané (lot, manifeste)

  • Pris par la session principale avant l'envoi : D:\Game\LifePlay_Backup_5.5\REL-ALPHA\manifest.csv, l. 2 "Source\LifePlay\LPModGuard.cpp","True","19867","239C49D0FC1E7D04DFDFDD51A930F2C5","2026-10-14T16:16:54", l. 3 "Source\LifePlay\LPModGuard.h","True","4297","C09543BED1F8CF8311F3B058C2E40CE1","2026-10-14T16:16:54".
  • MD5 relevé avant l'Edit égal au manifeste (§ 1). Retour arrière possible : Tools\Restore.ps1 -Lot REL-ALPHA (proposé seulement, décision de l'orchestrateur).

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

  • Aucune compilation, aucun jeton UE (ni éditeur, ni Build.ps1, ni UBT) : compilabilité estimée seulement (§ Constats, API lues dans le moteur 5.8).
  • LPModGuard.h non modifié (E2) ; CompareVersions non touché ; aucun autre fichier de Source/ ni Config/ (le ProjectVersion=2026.10.2.1a est la fiche T1).
  • Aucun essai à l'exécution des 10 cas (déroulé à la main, § Constats) ; preuve en jeu prévue en T4 (SPEC § 4).
  • Aucune commande git qui écrit.

5. Points à confirmer en VAL

  • P1 : diff limité à ParseVersion (l. 52-92) de LPModGuard.cpp ; LPModGuard.h inchangé (MD5 c09543bed1f8cf8311f3b058c2e40ce1).
  • P2 : les 10 cas du tableau SPEC § 2, tracés dans le code (déroulé § Constats, lignes l. 60-91).
  • P3 : boucle des contrôles existants (l. 76-91) identique à l'ancienne (l. 58-73).
  • P4 : aucun UE_LOG ni message nouveau (10 UE_LOG avant et après).
  • P5 : FChar::IsAlpha absent (Grep IsAlpha : 0).
  • P6 : LPModGuard.cpp en LF, 0 CR, 0 octet non ASCII.
  • P7 : E1, ligne « chaîne vide » : 1 composante vide (pas 0), résultat illisible conforme ; libellé de la SPEC à corriger ou non.
  • P8 : E2, commentaire LPModGuard.h:19 jugé toujours vrai ; demi-ligne proposée, à accepter ou refuser.
  • P9 : compilabilité estimée (API citées § Constats) ; à confirmer par le Shipping T2 (pas de build Development d'essai, SPEC § 3).
  • P10 : E5, fragment 2>/dev/null dans une commande de vérification, à juger par l'orchestrateur.

6. Fichiers à commiter

  • M Source/LifePlay/LPModGuard.cpp (dépôt du jeu, D:\Game\LifePlay-master 5.8, branche ue5.8)
  • A Docs/Migration/etat/REL/ALPHA/REL-ALPHA-V2-IMPL.md (même dépôt, même branche)

Tours : 19 / 60

Constats

Déroulé à la main des 10 cas sur le nouveau code

Code : Text.TrimStartAndEnd() (l. 59, ne retire que les blancs de début et de fin), découpe sur . sans retrait des vides (l. 59), retrait des lettres ASCII de fin de la dernière composante (l. 61-75), contrôles inchangés (l. 76-91).

# Entrée Composantes après découpe (l. 59) Dernière, après retrait (l. 61-75) Contrôle décisif Résultat Attendu SPEC Conforme
1 2026.10.2.0 2026,10,2,0 0 : '0' hors plages, break (l. 70), LeftInline(1) sans effet toutes chiffres, ≤ 9 (l. 78-88) ; Out.Num()=4 (l. 91) lisible {2026, 10, 2, 0} lisible, 2026, 10, 2, 0 oui
2 2026.10.2.1 2026,10,2,1 1 (inchangée) idem lisible {2026, 10, 2, 1} lisible, 2026, 10, 2, 1 oui
3 2026.10.2.1a …,1a 'a' dans a..z → Keep=1 ; '1' → break ; LeftInline(1) → 1 idem lisible {2026, 10, 2, 1} lisible, 2026, 10, 2, 1 oui
4 2026.10.2.1A …,1A 'A' dans A..Z → 1 idem lisible {2026, 10, 2, 1} lisible, 2026, 10, 2, 1 oui
5 2026.10.2.1rc …,1rc 'c', 'r' retirés → 1 idem lisible {2026, 10, 2, 1} lisible, 2026, 10, 2, 1 oui
6 2026.10.2.1-a …,1-a 'a' retiré ; '-' (0x2D) hors plages → break → 1- '-' n'est pas un chiffre (l. 84) → return false (l. 86) illisible illisible (- reste) oui
7 2026.10.2.a …,a 'a' retiré → Keep=0, sortie du while (l. 65) ; LeftInline(0) → vide Part.IsEmpty() (l. 78) → return false (l. 80) illisible illisible (composante vide) oui
8 2026.10a.2.1 2026,10a,2,1 1 (inchangée ; 10a n'est pas la dernière) 'a' de 10a n'est pas un chiffre (l. 84) → false illisible illisible (lettre hors dernière) oui
9 2026.10.2.1 a …,1 a (espace interne gardé par TrimStartAndEnd) 'a' retiré ; ' ' (0x20) hors plages → break → 1 ' ' n'est pas un chiffre (l. 84) → false illisible illisible (l'espace reste) oui
10 chaîne vide 1 composante vide (E1) Keep=0, while non exécuté, LeftInline(0) sans effet Part.IsEmpty() (l. 78) → false illisible illisible (« 0 composante ») oui (libellé E1)

Remarque (estimé, comportement d'avant inchangé) : en cas d'échec après des composantes valides (cas 7, 9), Out garde les composantes déjà lues ; les appelants n'utilisent pas Out quand la fonction rend faux (l. 291 puis bGameParsed && l. 399 ; l. 385 → échec, BuiltParts non lu ; l. 292, constante valide).

API moteur utilisées (UE 5.8.3, D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Core)

  • TArray::Last(SizeType IndexFromTheEnd = 0) non const : Public/Containers/Array.h:1502-1506 (RangeCheck, appel gardé par Parts.Num() > 0, l. 61).
  • FString::operator[](int32) non const : Public/Containers/UnrealString.h.inl:244-248 (checkf(IsValidIndex) ; indice Keep - 1 dans [0, Len()-1] car 0 < Keep <= Len()).
  • FString::LeftInline(int32 Count, EAllowShrinking = EAllowShrinking::Default) : UnrealString.h.inl:962-967 → RemoveAt(Count, Length-Count, …) (Private/Containers/String.cpp.inl:707-710) → TArray::RemoveAt(Index, Count, …) (Array.h:2347-2352, rien si Count == 0) ; RangeCheck(0, 0) sur un tableau vide passe (Array.h:1356, 0 + 0 <= 0) : cas 10 sans assertion.
  • FString::ParseIntoArray(TArray&, const TCHAR*, bool) : Private/Containers/String.cpp.inl:1356-1370 ; UE::String::ParseTokens : Private/String/ParseTokens.cpp:32, 40-87, 290-292.
  • LPModGuard.* n'existe que dans la copie 5.8 (Glob Source/LifePlay/LPModGuard.* dans D:\Game\LifePlay-master 5.5 : aucun fichier) ; pour mémoire, LeftInline(int32, EAllowShrinking = EAllowShrinking::Yes) existe aussi en 5.5.4 (UE_5.5/.../UnrealString.h.inl:1145).
  • Pas de masquage de nom : le const TCHAR C du bloc neuf (l. 67) et celui de la boucle (l. 82) sont dans des portées sœurs, non imbriquées (pas de C4456 attendu, estimé).