LifePlay-Revival/Docs/Migration/etat/ST/STF/STF-T5-VAL.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

38 KiB
Raw Blame History

Fiche STF-T5a-VAL — Niveau N1 — Modèle prévu sonnet | utilisé claude-sonnet-5-5 — Agent lp-validator — Skills sk-protocol, sk-recount, debug-verification, lp-validate

STF-T5-VAL : VAL statique de la partie (a) de STF-T5, MainMenu SpinBox_0 -> SpinBoxDOF (2026-10-10)

Copie D:\Game\LifePlay-master 5.8, branche ue5.8. Tag STF-T5-avant = tag annoté sur 4074d90 (= HEAD à la relecture ; git rev-parse des deux : 4074d90cecb0229c874ddeca52a5c88ad8c54adb). Validateur sans éditeur, commandlet, build, MCP ; sans écriture dans Content / Config / Source ; sans git en écriture. Seul fichier écrit : ce livrable (LF, comme le dossier etat/ST). Partie (b) (CharGen) : hors de cette VAL, bloquée par la fiche. Heures des journaux d'éditeur en UTC (local = UTC + 2 h).

Méthode : avant = git show STF-T5-avant:Content/UI/MainMenu.uasset (MD5 00e42d72c773a55f6998ae67662fdb76, 2 246 412 o), après = fichier de la copie (MD5 7292c6beb26a8066343abddf49b63d9f, 2 260 418 o, copié dans le scratchpad hors dépôt). Lecture par Tools/uasset58.py (locate, events, props, python -I) et par mes scripts du scratchpad (entry.py, diffexp2.py, t5.py, edges2.py, props.py). Contrôle d'intégrité du décodeur : exports contigus dans les deux états (affiché par uasset58.py : contigus=True, 1722 puis 1727 exports ; tables de noms 1219 puis 1224).

1. Fiches amont relues

STF-T5-CART.md (§ 1-3), STF-T5-SPEC.md (§ 0-5, partie (a) = § 1, critères W1-W6 et J = § 4), STF-T5-IMPL.md (§ 1-5). Relu aussi pour le contexte : mes STF-VAL.md (STF-T4) et STF-SPEC.md § 7. Formes (gabarits lp-doc) : STF-T5-IMPL.md a un en-tête et un titre, mais ni les sections IMPL numérotées (1 fichiers modifiés, 2 écarts, 3 instantané, 4 non fait, 5 points à confirmer, 6 fichiers à commiter A / M / D), ni la ligne Tours ; STF-T5-CART.md n'a pas les sections CART 4-6 ni Tours. Sans effet sur le fond.

2. Points à confirmer des IMPL

STF-T5-IMPL.md n'a pas de section « points à confirmer » ; je relève ce qu'il déclare ou renvoie à la VAL.

# Point (source) Verdict Preuve
P1 MD5 avant / après MainMenu ; CharGen et GI inchangés (IMPL tableau) PASS (CharGen 9669b103… = valeur T5-T0 / IMPL ; GI 606eb437… = celle de STF-T5-avant) § 3 C0
P2 SpinBoxDOF : 5 occurrences, Get_SpinBox_0_Value_0 : 0 (IMPL § 2) PASS C1
P3 Événements : 47 (OnClicked 32, OnValueCommitted 14, OnValueChanged 1), SpinBox_0 absent (IMPL § 2) PASS C2
P4 Écart de méthode : lecteurs purs (VariableGet_20, _21, GetArrayItem_3, VariableGet_27, _28) partagés entre OnValueCommitted et Construct (IMPL § 1) PASS-STATIC (graphe lu au niveau nœud : mêmes nœuds cités des deux côtés) C3, C6
P5 Sorties « faux » : OnValueCommitted non branchées ; Construct : les trois rejoignent UpdateDonors (IMPL § 1) PASS-STATIC (niveau nœud) C6
P6 Nœuds orphelins : aucun (IMPL § 1) PASS-STATIC : aucun export orphelin additionnel dans le diff (C3) ; les 22 exports ajoutés sont tous cités par la chaîne (C6). Un nœud pur sans lien serait invisible au niveau nœud C3, C6
P7 PIE : champ = 2 (1 × 2), saisie 4 -> DepthOfFieldFarBlurSize 2 (IMPL § 3 W3 / W4) reporté : relevés MCP de l'orchestrateur (établi par lui), non rejouables sans éditeur ; valeurs de GI absentes du journal C7
P8 Journal PIE : Get_SpinBox_0_Value_0 0, MainMenu_C 0, 4 PIE: Error hors périmètre (IMPL § 3) PASS (recompté) C7
P9 W1 : 418 / 418, Success - 0 error(s), 0 warning(s), MainMenu l. 2802 (IMPL § 4) PASS (recompté) C5
P10 Suppression de la fonction sans autre référence (IMPL § 1) : 5 assets référencent MainMenu reporté : relevé find_nodes / grep de l'orchestrateur non rejoué ; le diff d'exports montre que seuls le widget, ComponentBoundEvent_19 et le graphe Get_SpinBox_0_Value_0 ont disparu (C3)
P11 Changement de comportement à accepter (SPEC § 1, fin) : le champ ne suit plus le flou en continu à confirmer par l'utilisateur (case 3, § 6) —

3. Critères rejoués (commande -> compte brut)

C0 — État de la copie : PASS

Commande Résultat brut
git rev-parse "STF-T5-avant^{commit}" ; git rev-parse HEAD 4074d90cecb0229c874ddeca52a5c88ad8c54adb pour les deux ; git cat-file -t STF-T5-avant = tag
git status --short -- Content Config Source 2 lignes : M Content/UI/CharGen.uasset, M Content/UI/MainMenu.uasset
md5sum Content/UI/MainMenu.uasset 7292c6beb26a8066343abddf49b63d9f (attendu)
git show STF-T5-avant:Content/UI/MainMenu.uasset | md5sum 00e42d72c773a55f6998ae67662fdb76 (attendu ; = instantané manifest.csv 00E42D72…)
md5sum Content/BPs/GI.uasset ; git show STF-T5-avant:Content/BPs/GI.uasset | md5sum 606eb43714c1f4317300b797edd02e62 ; 606eb43714c1f4317300b797edd02e62 : identiques
md5sum Content/UI/CharGen.uasset 9669b103d0a056f0bd9089936d848963 : la modification de CharGen (git diff --stat : 5 028 080 -> 5 047 338 o) est celle de STE-TXT (même MD5 que T5-T0 et IMPL), pas de cette partie (a)
git diff --stat STF-T5-avant -- Content Config Source 2 fichiers binaires : CharGen.uasset (STE-TXT, hors lot) et MainMenu.uasset 2 246 412 -> 2 260 418 o ; rien dans Config / Source
md5sum Saved/SaveGames/*.sav GameSettings.sav 590b2cc503344f5b76971517b1de098e, Default.sav 81f13c39eaf5dde64981fae83b1ef70a = manifest.csv de STF-T5 (590B2CC5…, 81F13C39…) : W6 PASS

C1 — W2, liaisons : PASS (établi sur fichier ; asset non chargé par le moteur)

python -I Tools/uasset58.py locate <asset> SpinBox_0 SpinBoxDOF Get_SpinBox_0_Value_0 SpinBoxSexDuration SliderCameraSpeed :

Nom Avant Après
SpinBox_0 6 (2 exports SpinBox dans WidgetTree, 2 dans MainMenu WidgetBlueprint à +8026 et +47583, 2 dans MainMenu_C à +5829 et +15863) 0
SpinBoxDOF 0 5 (table des noms + 2 exports SpinBox + 1 dans MainMenu +46415 + 1 dans MainMenu_C +15328)
SpinBoxSexDuration (témoin sans liaison) 5 5
SliderCameraSpeed (témoin sans liaison) 5 5
Get_SpinBox_0_Value_0 (chaîne entière) 0 0
Remarque : l'attendu « 0 après » est vrai mais ne distingue pas l'avant, car le nom s'écrit en table sous la forme FName « Get_SpinBox_0_Value » + suffixe 1 (la chaîne …_Value_0 n'existe dans aucun des deux
états). La preuve utile est la table des noms (python t5.py) : le nom Get_SpinBox_0_Value est dans la liste des noms retirés (avec BndEvt__SpinBox_0_K2Node_ComponentBoundEvent_19_… et
K2Node_FunctionResult_ReturnValue_ImplicitCast) ; il n'existe plus en table après.
Profil de SpinBoxDOF = celui des widgets sans liaison (5 = table + 2 exports + 1 + 1 ; une liaison en ajouterait 2) : aucune liaison sur SpinBoxDOF.
Tableaux de liaisons (python entry.py <asset> MainMenu 0 12000 et … MainMenu_C 0 9000 : FString ObjectName suivie de la balise PropertyName) :
Tableau Avant Après
--- --- ---
éditeur (MainMenu) 19 18
exécution (MainMenu_C) 19 18
Après : plus aucune entrée SpinBox dans les deux tableaux (le filtre SpinBox ne retourne rien). 19 - 1 = 18 : attendu confirmé.

C2 — Événements : PASS-STATIC

python -I Tools/uasset58.py events <asset> :

Avant Après
nœuds ComponentBoundEvent 47 47
OnClicked 32 32
OnValueCommitted 14 (dont _19 = SpinBox_0) 14 (dont _6 = SpinBoxDOF, export #1047)
OnValueChanged 1 (_30 SliderCameraSpeed) 1 (_30)
Les 13 autres OnValueCommitted (_31, _32-_37, _39, _40, _43-_46) sont inchangés. _19 (avant) est rattaché à SpinBox_0 par son ComponentPropertyName (FName « SpinBox » + suffixe, que le filtre
de l'outil n'affiche pas) ; _6 (après) porte SpinBoxDOF. Bruit : dans l'état après, l'outil ajoute le widget « Version » à tous les OnValueCommitted (le décalage de 5 indices de noms fait coïncider un indice avec
une valeur répétée) : faux positif, sans lien avec les widgets.

C3 — Diff des exports avant / après : PASS, avec écarts d'annonce (ci-dessous)

python diffexp2.py (clé = chemin d'imbrication + nom + classe ; 1722 clés distinctes avant, 1727 après, aucun doublon) : 17 retirés, 22 ajoutés, net +5 = 1727 - 1722.

Retirés (17) Ajoutés (22)
2 SpinBox SpinBox_0 (WidgetTree de MainMenu et de MainMenu_C) 2 SpinBox SpinBoxDOF
K2Node_ComponentBoundEvent_19 K2Node_ComponentBoundEvent_6
EdGraph Get_SpinBox_0_Value_0 + Function Get_SpinBox_0_Value_0 (classe) —
ses 9 nœuds : FunctionEntry_0, FunctionResult_0, BreakStruct_0, CommutativeAssociativeBinaryOperator_0, GetArrayItem_0, VariableGet_0 à _3 —
Function BndEvt__SpinBox_0_K2Node_ComponentBoundEvent_19_… Function BndEvt__MainMenu_SpinBoxDOF_K2Node_ComponentBoundEvent_6_…
2 HorizontalBoxSlot_1 sous HorizontalBox_34 2 HorizontalBoxSlot_0 sous HorizontalBox_34
— K2Node_IfThenElse_10 à _15 (6)
— K2Node_CallFunction_93 à _97 (5)
— K2Node_CallArrayFunction_3, _4 ; K2Node_BreakStruct_1 ; K2Node_PromotableOperator_0 ; K2Node_VariableGet_83
Conforme aux attendus de la fiche : tous les ajouts attendus (SpinBoxDOF, ComponentBoundEvent_6, IfThenElse_10 à _15, CallFunction_93 à _97, CallArrayFunction_3 et _4, BreakStruct_1,
PromotableOperator_0, VariableGet_83) sont présents ; tous les retraits attendus (widget, ComponentBoundEvent_19, graphe et nœuds de Get_SpinBox_0_Value_0) sont présents.
Autres changements à signaler (rien d'inattendu dans les nœuds de jeu) :
  1. Slots de HorizontalBox_34 : le slot par défaut de 71 octets de l'enfant SpinBox est renommé HorizontalBoxSlot_1 -> HorizontalBoxSlot_0 (×2, même taille 71 o) ; l'autre slot (HorizontalBoxSlot_3, 300 o, celui du libellé TextBlock_14) est inchangé. Nom d'objet auto-attribué par AddWidget ; sans effet. L'indice de l'enfant (1) n'est pas relu par ce décodage (relevé MCP de l'orchestrateur).
  2. Fonction BndEvt__ : remplacée comme attendu (le nom contient désormais MainMenu_ et SpinBoxDOF).
  3. 9 exports communs changent de taille (diffexp2.py, « COMMUNS dont la taille change » : 9), tous expliqués par l'arithmétique des liens (une liaison de broche = 24 octets) sauf deux résidus :
    Export Avant -> après Explication
    EventGraph 1957 -> 2021 (+64) 17 nœuds ajoutés - 1 retiré = +16 références x 4 o
    K2Node_VariableGet_20 / _21 / _27 +48 chacun 2 nouveaux liens chacun (CallFunction_93 + _95 ; CallArrayFunction_3 + _4 ; CallFunction_94 + _96)
    K2Node_VariableGet_28 +24 1 nouveau lien (BreakStruct_1)
    K2Node_CallFunction_1278 (UpdateDonors) 989 -> 1061 (+72) entrée d'exécution : 1 source (_92) -> 4 sources (_97 + 3 sorties « faux ») = +3 liens
    ExecuteUbergraph_MainMenu 31 184 -> 32 889 (+1 705) code d'exécution du graphe d'événements (non décodé)
    MainMenu (WidgetBlueprint) 93 899 -> 93 187 (-712) -562 (entrée de liaison éditeur retirée) ; résidu -150 non attribué
    MainMenu_C (classe) 27 866 -> 27 448 (-418) -403 (entrée de liaison exécution retirée) ; résidu -15 non attribué
    Les résidus (-150, -15) ne sont pas expliqués à l'octet (estimé : références à la fonction supprimée et au widget renommé dans les tables de ces deux exports) ; ils n'ont pas de contrepartie dans les nœuds ni dans les
    propriétés relues. Incertain : aucun essai ne tranche sans éditeur.
  4. Le reste des exports (1722 - 9 - 17 retirés = 1696 exports communs inchangés en taille) ne change pas de taille. Les octets d'un export commun ne sont pas comparés (les indices de noms et d'exports ont glissé de +5 / +5).

C4 — Valeurs de SpinBoxDOF : PASS-STATIC (établi sur fichier)

python -I Tools/uasset58.py props <après> SpinBoxDOF et python edges2.py (export #1493 et copie #1507, identiques ; propriétés stockées en delta par rapport au défaut du moteur) :

Propriété Dans l'asset Valeur
MaxValue présente, float 0x41a00000 20.0
MaxSliderValue présente, float 0x41a00000 20.0
bOverride_MinValue / MaxValue / MinSliderValue / MaxSliderValue présentes, drapeau 0x10 vrai (x4)
MinValue, MinSliderValue absentes (défaut moteur) 0
Value absente (défaut moteur) 0 (valeur de conception de la SPEC)
Delta, MinFractionalDigits, MaxFractionalDigits, bAlwaysUsesDeltaSnap absentes défauts moteur (0, 1, 6, faux) : conforme à la SPEC (Delta 0, décimales 1 / 6 ; pas d'arrondi entier, E3 de STF ne s'applique pas à ce widget)
Min 0 / Max 20, glisser 0-20 : conforme. Le style, la police (Roboto Bold 20) et le slot ne sont pas relus octet à octet (IMPL : « 34 propriétés, 0 écart » : reporté, non rejoué).
B = 2 sur PromotableOperator_0 : la chaîne 2.0 apparaît une fois dans l'export (+680, valeur par défaut d'une broche) ; le nom de la broche porteuse (B) est un FName, non résolu par ce décodage :
estimé (une seule constante 2.0 dans un opérateur « Multiplier » dont l'autre entrée est reliée à BreakStruct_1) ; la classe PromotableOperator et le nom Multiply sont présents (noms ajoutés : Multiply,
OperationName, K2Node_PromotableOperator).

C5 — W1, CompileAllBlueprints : PASS

Journal Saved\Logs\portage\STF_CompileAllBlueprints_20261010-135203.log (482 232 o ; ouvert 13:52:04 locale, soit après la sauvegarde de l'asset à 13:45 : il compile bien 7292c6be…) :

Commande Compte brut
grep -c 'Loading and Compiling' 418
grep -c 'a réussi' 418
grep -c 'échoué' 0
grep -c 'Error:' 0
grep -n 'Compiling Completed' l. 3235 : Compiling Completed with 0 errors and 0 warnings and 0 blueprints that failed to load.
grep -n 'Success -' l. 3242 : LogInit: Display: Success - 0 error(s), 0 warning(s)
lignes MainMenu l. 2800 Loading and Compiling: '/Game/UI/MainMenu.MainMenu' ; l. 2802 [0125,84] La compilation de MainMenu a réussi ! [en 480 ms]
IMPL § 4 (418 / 418, Success, MainMenu l. 2802) confirmé. Exécution du commandlet par l'orchestrateur : non rejouée (interdit ici).

C6 — Chaînes de nœuds (écriture et Construct) : PASS-STATIC (niveau nœud, estimé)

python edges2.py (références croisées nœud à nœud : A cite B et B cite A ; sans lecture des broches) : 30 liens attendus, 30 présents.

  • OnValueCommitted (12) : ComponentBoundEvent_6 -> IfThenElse_10 (CallFunction_93 <- VariableGet_20) -> IfThenElse_11 (CallArrayFunction_3 <- VariableGet_21) -> IfThenElse_12 (CallFunction_94 <- VariableGet_27) -> SetFieldsInStruct_0 -> VariableSet_13 ; ComponentBoundEvent_6 <-> CommutativeAssociativeBinaryOperator_9 (InValue x 0.5). ComponentBoundEvent_6 n'est pas lié directement à VariableSet_13.
  • Construct (18) : CallFunction_92 -> IfThenElse_13 (CallFunction_95 <- VariableGet_20) -> IfThenElse_14 (CallArrayFunction_4 <- VariableGet_21) -> IfThenElse_15 (CallFunction_96 <- VariableGet_27) -> CallFunction_97 (SpinBoxDOF.SetValue : VariableGet_83, PromotableOperator_0 <- BreakStruct_1 <- VariableGet_28) -> CallFunction_1278 ; les 3 IfThenElse_13 / _14 / _15 <-> CallFunction_1278 (sorties « faux »). L'ancien lien CallFunction_92 <-> CallFunction_1278 : absent (False). Conforme à SPEC § 1.2-1.3 et à IMPL § 1 au niveau nœud. Non établi sans éditeur : lequel des deux pins (vrai / faux) de chaque IfThenElse porte chaque lien, la valeur littérale 0 de l'index de IsValidIndex, le facteur 0.5 conservé (la constante de CommutativeAssociativeBinaryOperator_9 n'est pas relue), la cible exacte de SetValue (self).

C7 — Journal PIE STF-T5a_Editor_20261010-133950.log depuis « Created PIE world » : PASS

Fichier 362 456 o, 2709 lignes, ouvert 13:39:50 locale ; sauvegarde de MainMenu Saving Package: /Game/UI/MainMenu l. 2414 (11:45:50 UTC), avant le PIE.

Commande (à partir de la ligne 2457, tail -n +2457) Compte brut
grep -n 'Created PIE world' 1 occurrence, l. 2457 (11:48:04.315 UTC)
grep -c 'Get_SpinBox_0_Value_0' 0 (0 aussi dans tout le journal)
grep -c 'MainMenu_C' 0
grep -c 'SpinBox_0' / 'SpinBoxDOF' 0 / 0
grep -c 'PIE: Error' 4
grep -c 'LogScript: Warning' 4 (les mêmes accès)
grep -n 'Shutting down PIE' l. 2698 (11:51:30.977 UTC) ; Fatal error / Assertion failed / Unhandled : 0 ; pas de LogExit dans le journal (éditeur encore ouvert à la dernière ligne)
Les 4 PIE: Error restants, tous à 11:48:04.584 UTC (l. 135, 139, 143, 147 depuis la ligne 2457) :
  1. Accès à "Aucun" … la propriété StreetMapStatic dans /Game/BPs/GI.GI_C — nœud Branche, worksHere, Blueprint Person… ;
  2. Accès à None, lecture de la structure Amenity — Branche, worksHere ;
  3. Accès à "Aucun" … StreetMapStatic dans /Game/BPs/GI.GI_C — nœud retour, worksHere ;
  4. Accès à None, lecture de la structure Amenity — nœud retour, worksHere. Aucune ne concerne MainMenu ni SpinBoxDOF : hors périmètre (carte de rues absente), conformes à IMPL § 3 (« 4 PIE: Error hors périmètre »). Avant (T4 de STF) : 12 erreurs MainMenu_C et 60 lignes Get_SpinBox_0_Value_0 ; le gain se confirme. Limite : un journal sans erreur ne prouve pas que le champ affiche 2 ni que 4 -> 2 : ces relevés (W3, W4) sont des relevés MCP de l'orchestrateur, non rejouables ici.

4. Diff lu en entier

git diff --stat STF-T5-avant -- Content Config Source : 2 fichiers binaires, dont MainMenu.uasset (partie (a)) et CharGen.uasset (STE-TXT, hors lot). Aucun diff texte à lire : le « diff » de (a) est le diff d'exports de C3 (17 retirés / 22 ajoutés / 9 tailles communes modifiées, dont 2 résidus non attribués) complété par C1, C2, C4 et C6. Fins de ligne : sans objet pour un .uasset ; ce livrable est en LF.

5. Verdict

Verdict global : PASS-STATIC pour la partie (a) (aucun FAIL ; deux réserves ci-dessous). Par contrôle :

  • C0 état et W6 fichiers : PASS
  • C1 W2 liaisons : PASS (18 / 18 entrées, plus de liaison, SpinBoxDOF sans liaison)
  • C2 événements : PASS-STATIC (47 / 47, 14 / 14, 1)
  • C3 diff d'exports : PASS (attendus tous présents ; slots renommés et deux résidus de taille signalés)
  • C4 valeurs de SpinBoxDOF : PASS-STATIC (Min 0, Max 20, glisser 0-20, Value 0 ; B = 2 estimé)
  • C5 W1 : PASS (418 / 418, 0 / 0)
  • C6 chaînes : PASS-STATIC (30 / 30 au niveau nœud)
  • C7 journal PIE : PASS (0 / 0, 4 erreurs hors périmètre)
  • W3, W4 (champ = 2, saisie 4 -> 2, réouverture) : établi par l'orchestrateur, non rejouable ; J (archive Development) : à faire par l'utilisateur. Réserves : (1) résidus de taille -150 (MainMenu) et -15 (MainMenu_C) non expliqués à l'octet ; (2) équivalence de comportement lue au niveau nœud, pas broche à broche. Niveau de preuve : décodage du fichier établi (exports contigus) ; équivalence de comportement estimée.

6. Points à confirmer par l'utilisateur

  • 1. Accepter le changement de comportement de SPEC § 1 : le champ « Depth of Field Strength » n'est plus réévalué à chaque image ; il affiche la valeur lue à l'ouverture du menu (Construct) et celle que le joueur valide.
  • 2. Accepter l'écart de méthode IMPL § 1 (lecteurs purs partagés entre OnValueCommitted et Construct) : sans effet attendu, un nœud pur est réévalué à chaque usage.
  • 3. Preuve en jeu J (archive Development) : le champ se règle, flou désactivé à 0, 0 occurrence de Get_SpinBox_0_Value_0 au journal. Non faite.
  • 4. Faire ranger les scripts du scratchpad dans Saved/STF/ ou Tools/ s'ils doivent être rejoués (ils réutilisent Tools/uasset58.py pour locate, events, props).
  • 5. Corriger les sections manquantes des gabarits (IMPL : 3-6 et Tours ; CART : 4-6 et Tours) — hors périmètre du validateur.

Tours : 9 / 20

Constats

  • CharGen.uasset est modifié dans l'arbre de travail (M, STE-TXT, MD5 9669b103… stable depuis T5-T0) : ne pas l'inclure dans le commit de la partie (a) (ticket STF-R3, MainMenu.uasset seul).
  • Le décodeur compare des tailles d'exports et des références croisées ; il ne lit pas les broches. Pour trancher les deux résidus de taille et les liens vrai / faux des IfThenElse, il faudrait l'éditeur (get_node_infos) : proposition d'essai rejouable par l'orchestrateur, non faite ici.
  • Aucune nouveauté non demandée vue en passant.

7. Décisions sur § 6 (orchestrateur, 2026-10-10)

  • Changement de comportement (affichage lu à l'ouverture du menu) et méthode : déjà acceptés par l'utilisateur (« (a) approuvée » sur STF-T5-SPEC § 1). Écart de méthode (lecteurs purs partagés, showPinForProperties) : sans effet sur le comportement, consigné dans STF-T5-IMPL § 1.
  • Commit de (a) avant la preuve en jeu : décision de l'utilisateur (« Maintenant ») ; J de T5 sur la prochaine archive, avec (b).
  • Scripts d'analyse de cette passe : jetables (l'outil réutilisable est Tools/uasset58.py, rangé avec STF).
  • Gabarits de T5-CART / T5-IMPL : sections non applicables omises (lecture seule / IMPL MCP), sans effet sur les preuves.
  • Résidus de taille inexpliqués (−150 sur MainMenu, −15 sur MainMenu_C) et B = 2 estimé : acceptés (PIE W4 : 4 tapé → flou 2, et champ 2 pour un flou 1, établis par relevé MCP).

Partie (b) CharGen

Fiche STF-T5b-VAL — Niveau N1 — Modèle prévu sonnet | utilisé claude-sonnet-5-5 — Agent lp-validator — Skills sk-protocol, sk-recount, debug-verification, lp-validate

VAL statique de la partie (b) de STF-T5 (CharGen, SpinBox_0 « Counteract Vagina Deform »), ajoutée le 2026-10-10 sans toucher à la partie (a) ci-dessus. Mêmes interdits (aucun éditeur, commandlet, MCP, git en écriture ; seul ce livrable est écrit, en LF). STF-T5b-avant = tag annoté (git cat-file -t : tag) sur 073f9dfe2466070fdb68f3bcc089266ba1378a66 (STE-R3) ; HEAD à la relecture : e3df8ca (OBP-T2, sans effet sur Content). Méthode : avant = git show STF-T5b-avant:Content/UI/CharGen.uasset (MD5 a0de37acc2cfcb02ef254af2517e4e20, 5 046 138 o) ; après = fichier de la copie (MD5 e4c2eea6a3a2129eb8b382715609b1d5, 5 054 038 o, 15:30 locale) ; Tools/uasset58.py (locate, events, python -I) et scripts du scratchpad (diffexp2.py, cg.py, cg2.py, entry.py). Exports contigus dans les deux états (3582 puis 3586 exports ; tables de noms 1600 puis 1602).

b1. Fiches amont relues

STF-T5-SPEC.md § 2 (partie (b) : garde seule, OnValueChanged conservé, effet en direct voulu), STF-T5-IMPL.md § 6 (en entier), STF-T5-CART.md § 2 (déjà relu en (a)). Écart de l'IMPL à la SPEC : Delta 0 -> 0.5, bAlwaysUsesDeltaSnap vrai, décimales 1 / 1 : ajout décidé par l'utilisateur pendant le PIE (IMPL § 6), absent de la SPEC § 2 ; il est contrôlé en C2b.

b2. Points à confirmer de l'IMPL § 6

# Point (source) Verdict Preuve
Q1 MD5 avant / après CharGen ; MainMenu, GI, StatMenu inchangés (IMPL tableau) PASS C0b
Q2 Garde ComponentBoundEvent_85 -> IfThenElse_37 (IsValid GI) -> IfThenElse_38 (IsValid CharacterBeingEdited) -> CallFunction_303 SetMorphTarget, sorties « faux » non branchées PASS-STATIC (niveau nœud) C1b, C4b
Q3 OnValueChanged gardé (pas de passage à OnValueCommitted) PASS : 15 OnValueChanged avant et après C3b
Q4 Delta 0.5, bAlwaysUsesDeltaSnap, décimales 1 / 1, bornes inchangées PASS-STATIC (établi sur fichier) C2b
Q5 « Aucun doublon, IfThenElse_39 inexistant » (incident « session expired ») PASS : exactement 2 IfThenElse ajoutés (_37, _38), aucun _39 C1b
Q6 W1 : 418 / 418, Success - 0 error(s), 0 warning(s), CharGen l. 2800 PASS (recompté) C5b
Q7 PIE : property GI in /Game/UI/CharGen 0 ; restent 4 worksHere + 1 PopulateGenitalSlidersDaz PASS (recompté) C6b
Q8 Effet en direct conservé pendant le glisser (observé par l'utilisateur) déclaré par l'utilisateur, non rejouable ici —
Q9 Pas de 0.5 : « non rejoué en PIE (réglé après) » confirmé par le journal : 2e sauvegarde de CharGen à 13:30:28 UTC, après l'arrêt du PIE (13:30:04) ; établi par relecture des propriétés (C2b) C2b, C6b
Q10 .sav inchangés, aucun préréglage créé PASS pour les .sav ; « aucun préréglage » : non rejoué (fichiers hors Saved/SaveGames) C0b

b3. Critères rejoués (commande -> compte brut)

C0b — État des fichiers : PASS

Commande Résultat brut
git cat-file -t STF-T5b-avant ; git rev-parse "STF-T5b-avant^{commit}" tag ; 073f9dfe2466070fdb68f3bcc089266ba1378a66
git status --short -- Content Config Source 1 ligne : M Content/UI/CharGen.uasset
git diff --stat STF-T5b-avant -- Content Config Source `Content/UI/CharGen.uasset
git show STF-T5b-avant:Content/UI/CharGen.uasset | md5sum ; md5sum Content/UI/CharGen.uasset a0de37acc2cfcb02ef254af2517e4e20 (= instantané STF-T5b\manifest.csv) ; e4c2eea6a3a2129eb8b382715609b1d5 (attendu)
md5sum Content/UI/MainMenu.uasset 7292c6beb26a8066343abddf49b63d9f (attendu)
md5sum Content/BPs/GI.uasset 606eb43714c1f4317300b797edd02e62 (attendu)
md5sum Content/UI/StatMenu.uasset 9030531ccf4ef46c133722dacd6d2203 (préfixe 9030531c attendu)
md5sum Saved/SaveGames/GameSettings.sav ; Default.sav 590b2cc503344f5b76971517b1de098e ; 81f13c39eaf5dde64981fae83b1ef70a = manifest.csv de STF-T5b (590B2CC5…, 81F13C39…)

C1b — Diff des exports : PASS

python diffexp2.py CharGen_avant.uasset CharGen_apres.uasset (clé = chemin d'imbrication + nom + classe ; 3582 clés avant, 3586 après, 0 doublon) : 1 retiré, 5 ajoutés, net +4 = 3586 - 3582.

Ajoutés (attendus) Statut
K2Node_IfThenElse_37 et _38 (CharGen/EventGraph) présents
K2Node_CallFunction_59 et _93 (CharGen/EventGraph, IsValid) présents
ComponentDelegateBinding_3 (CharGen_C), en remplacement de ComponentDelegateBinding_1 (retiré) non annoncé : objet régénéré à la compilation (même classe, même parent, un retiré / un ajouté) ; sans effet
Aucun autre ajout ni retrait. K2Node_IfThenElse_37 existe aussi dans le graphe GetVisibility_0 (même nom, autre chemin) : sans lien avec le nouveau nœud, mais à ne pas confondre dans une recherche par nom seul (mon premier test, par nom, a donné un faux négatif avant correction).
Changements de taille des exports communs : 6 (diffexp2.py) :
Export Avant -> après
--- ---
EdGraph EventGraph 5445 -> 5461 (+16)
Function ExecuteUbergraph_CharGen 102 920 -> 103 201 (+281)
K2Node_VariableGet_280 769 -> 793 (+24)
K2Node_VariableGet_281 854 -> 878 (+24)
SpinBox SpinBox_0 (CharGen et CharGen_C, 2 exports) 171 -> 254 (+83 chacun)
Écarts à l'annonce : (i) ComponentBoundEvent_85 et CallFunction_303 sont cités dans la fiche comme changeant de taille ; leur taille n'a pas changé (un lien remplacé par un lien : l'événement pointait sur CallFunction_303, il pointe sur IfThenElse_37 ; CallFunction_303 est
alimenté par IfThenElse_38 au lieu de l'événement) : cohérent, pas un écart de contenu ; (ii) SpinBox_0 (+83 octets x 2) change de taille : attendu par C2b, non listé dans la fiche. Aucun autre export (3586 - 4 ajoutés - 6 = 3576 communs inchangés en taille).
Résidu d'en-tête non attribué : l'en-tête passe de 2 240 841 à 2 244 168 (+3 327) ; 2 noms (57 o) + 4 exports (448 o) n'expliquent qu'une partie ; le reste (tables de dépendances de pré-chargement) n'est pas décodé : incertain, sans contrepartie dans les nœuds.

C2b — Propriétés de SpinBox_0 : PASS-STATIC (établi sur fichier)

python cg.py (propriétés stockées en delta par rapport au défaut moteur ; mêmes valeurs dans les deux exports de chaque état) :

Propriété Avant Après
Delta absente (défaut 0) présente, float 0x3f000000 = 0.5
bAlwaysUsesDeltaSnap absente (nom absent de la table) présente, drapeau 0x10 = vrai
MaxFractionalDigits absente (défaut 6) présente, entier 1 (nom ajouté à la table)
MinFractionalDigits absente (défaut 1) absente (défaut 1) : le nom n'est dans la table ni avant ni après, la valeur 1 = défaut moteur n'est jamais écrite ; résultat 1 / 1
MinValue absente (défaut 0) absente (0)
MaxValue absente (non surchargée, bOverride_MaxValue absent) absente (idem)
MaxSliderValue présente, float 0x42c80000 = 100.0 présente, 100.0
MinSliderValue absente (0) absente (0)
bOverride_MinValue / bOverride_MinSliderValue / bOverride_MaxSliderValue présentes, vrai présentes, vrai
Conforme à IMPL § 6 et à la demande : Delta 0.5, snap vrai, décimales 1 / 1 ; Min 0, glisser 0-100 (MaxSlider 100), Max non surchargé : inchangés. Les +83 octets = 29 + 25 + 29, sans autre propriété modifiée dans le widget.
Noms ajoutés à la table (1600 -> 1602) : bAlwaysUsesDeltaSnap, MaxFractionalDigits ; Delta y était déjà (aucun nom retiré).

C3b — Liaisons et événements : PASS

Contrôle Avant Après
uasset58.py locate : SpinBox_0 2 (tableaux CharGen +65038 et CharGen_C +23393 seulement) 2 (mêmes positions relatives +65038 / +23393)
uasset58.py locate : TextBlock_69 (témoin sans liaison) 4 4
entry.py : entrées des tableaux de liaisons CharGen / CharGen_C 32 / 32 32 / 32
entrées de liaison sur SpinBox_0 / sur un SpinBox 0 / 0 0 / 0
uasset58.py events : ComponentBoundEvent 115 (OnClicked 73, OnValueChanged 15, OnCheckStateChanged 7, OnHovered 7, OnUnhovered 7, OnReleased 2, OnPressed 2, OnTextChanged 2) 115 (mêmes valeurs)
SpinBox_0 n'a donc aucune liaison de propriété (ses 2 occurrences hors WidgetTree : variable du widget et table de classe, comme TextBlock_69 hors ses exports) ; tableaux inchangés en nombre ; ComponentBoundEvent_85 reste un OnValueChanged (export #2175 -> #2177).

C4b — Chaîne de la garde (niveau nœud) : PASS-STATIC (estimé)

python cg2.py (références croisées nœud à nœud, restreintes aux nœuds de CharGen/EventGraph pour éviter l'homonyme GetVisibility_0) :

Paire Avant Après
ComponentBoundEvent_85 <-> IfThenElse_37 absent vrai
IfThenElse_37 <-> CallFunction_59 (IsValid) ; CallFunction_59 <-> VariableGet_280 (GI) absents vrai ; vrai
IfThenElse_37 <-> IfThenElse_38 absent vrai
IfThenElse_38 <-> CallFunction_93 (IsValid) ; CallFunction_93 <-> VariableGet_281 (CharacterBeingEdited) absents vrai ; vrai
IfThenElse_38 <-> CallFunction_303 (SetMorphTarget) absent vrai
CallFunction_303 <-> VariableGet_282 (cible) vrai vrai (inchangé)
ComponentBoundEvent_85 <-> CallFunction_303 (ancien lien direct) vrai faux (coupé comme prévu)
ComponentBoundEvent_85 <-> CommutativeAssociativeBinaryOperator_28 ; CallFunction_303 <-> …_28 (× -1) vrai ; vrai vrai ; vrai (inchangés)
Conforme à SPEC § 2.1 et IMPL § 6 au niveau nœud : la garde est en série entre l'événement et SetMorphTarget, le facteur × -1 et la cible sont inchangés. Non établi sans éditeur : quelle sortie (vrai / faux) de chaque IfThenElse est reliée (les sorties « faux » non branchées ne sont pas visibles à ce niveau), la valeur littérale 'Genesis8Female__Chin Type 1 Assist', le bon fonctionnement de la garde en jeu.

C5b — V1, CompileAllBlueprints : PASS

Journal Saved\Logs\portage\STF_CompileAllBlueprints_20261010-153044.log (482 112 o ; ouvert 15:30:45 locale, soit après la sauvegarde de CharGen à 15:30 : il compile bien e4c2eea6…) :

Commande Compte brut
grep -c 'Loading and Compiling' 418
grep -c 'a réussi' 418
grep -c 'échoué' ; grep -c 'Error:' 0 ; 0
grep -n 'Compiling Completed' l. 3234 : Compiling Completed with 0 errors and 0 warnings and 0 blueprints that failed to load.
grep -n 'Success -' l. 3241 : LogInit: Display: Success - 0 error(s), 0 warning(s)
ligne CharGen l. 2798 Loading and Compiling: '/Game/UI/CharGen.CharGen' ; l. 2800 [0109,92] La compilation de CharGen a réussi ! [en 573 ms] (la ligne 2625 est CharGenSlider, autre Blueprint)
IMPL § 6 (418 / 418, Success, CharGen l. 2800) confirmé. Exécution du commandlet : non rejouée (interdit ici).

C6b — Journal du PIE STF-T5b_Editor_20261010-143622.log depuis « Created PIE world » : PASS

Fichier 405 483 o, 3078 lignes, ouvert 14:36:23 locale. Created PIE world : 1 occurrence, l. 2419 (12:40:46.338 UTC). Sauvegardes de CharGen : l. 2378 (12:38:04 UTC, avant le PIE : version avec la garde) et l. 3049 (13:30:28 UTC, après l'arrêt du PIE Shutting down PIE online subsystems l. 3033 à 13:30:04.756 : version avec le pas de 0.5, qui n'a donc pas été jouée en PIE, comme déclaré).

Commande (tail -n +2419) Compte brut
grep -c 'property GI in /Game/UI/CharGen|propriété GI dans /Game/UI/CharGen' 0 (0 aussi dans tout le journal ; 1 dans le journal J1 de STF-T4 selon la fiche)
grep -c 'GI dans /Game/UI' 0
grep -c 'PIE: Error' 5
grep -c 'LogScript: Warning' 9
Fatal error / Assertion failed / Unhandled 0
Les 5 PIE: Error restants :
1.-4. (12:40:46.605 UTC, l. 135, 139, 143, 147 depuis la ligne 2419) : StreetMapStatic dans /Game/BPs/GI.GI_C (×2) et Accès à None, lecture de la structure Amenity (×2), fonction Works Here, Blueprint Person… : carte de rues absente, hors périmètre, identiques à ceux de la partie (a).
5. (12:41:01.319 UTC, l. 396) : Accès à "Aucun" … CallFunc_GetSkeletalMesh_ReturnValue en dehors d'une Uclass, nœud Branche / ForEachLoop, fonction Populate Genital Sliders Daz, Blueprint CharGen : nœud d'origine, hors périmètre (candidat noté au registre par l'orchestrateur, IMPL § 6) ; il ne concerne ni SpinBox_0 ni GI.
Le gain se confirme : 0 « property GI in /Game/UI/CharGen » (1 avant). Limite : un journal propre ne prouve pas l'effet en direct du morph (déclaré par l'utilisateur) ni le comportement du pas de 0.5.

b4. Diff lu en entier

git diff --stat STF-T5b-avant -- Content Config Source : 1 fichier binaire (CharGen.uasset, 5 046 138 -> 5 054 038 o). Aucun diff texte ; le « diff » est le diff d'exports de C1b (1 retiré / 5 ajoutés / 6 tailles communes modifiées, tous annoncés ou expliqués, sauf le renommage de ComponentDelegateBinding et le résidu d'en-tête) complété par C2b-C4b.

b5. Verdict

Verdict global de la partie (b) : PASS-STATIC (aucun FAIL ; réserves ci-dessous). Par contrôle :

  • C0b état des fichiers (CharGen, MainMenu, GI, StatMenu, .sav) : PASS
  • C1b diff d'exports : PASS (4 ajouts attendus ; ComponentDelegateBinding_1 -> _3 et SpinBox_0 +83 o signalés ; ComponentBoundEvent_85 / CallFunction_303 sans changement de taille)
  • C2b propriétés de SpinBox_0 : PASS-STATIC (Delta 0.5, snap vrai, décimales 1 / 1, bornes inchangées)
  • C3b liaisons et événements : PASS (aucune liaison, 32 / 32 entrées, 115 / 115 événements, OnValueChanged 15)
  • C4b garde (niveau nœud) : PASS-STATIC
  • C5b V1 : PASS (418 / 418, 0 / 0, ligne CharGen)
  • C6b journal PIE : PASS (0 « property GI in /Game/UI/CharGen » ; 5 erreurs hors périmètre listées) Réserves : (1) garde et sorties « faux » lues au niveau nœud seulement ; (2) le pas de 0.5 n'a pas été joué en PIE (déclaré par l'IMPL, confirmé par le journal) ; (3) effet en direct du morph : déclaré par l'utilisateur ; (4) résidu d'en-tête (+3 327) non décodé. Preuve en jeu J de la partie (b) (archive Development : property GI in /Game/UI/CharGen.CharGen_C = 0, effet en direct, pas de 0.5) : à faire par l'utilisateur.

b6. Points à confirmer par l'utilisateur

  • 1. Accepter l'ajout du pas de 0.5 sur « Counteract Vagina Deform » (Delta 0.5, snap, 1 décimale) : décision prise pendant le PIE, hors SPEC § 2 ; la valeur affichée et tapée est arrondie au demi-pas (même nature que E3 de STF).
  • 2. Preuve en jeu J de (b) : glisser le champ en création de personnage (morph visible en direct, valeurs par pas de 0.5), 0 occurrence au journal.
  • 3. Commit : CharGen.uasset seul pour (b) (ticket STF-R5/R6 selon le gardien) ; ne pas y mêler MainMenu.uasset de (a) si (a) a son propre commit.
  • 4. Populate Genital Sliders Daz (GetSkeletalMesh nul) : décider s'il devient un lot séparé (hors STF).

Tours (b) : 11 / 20

b7. Décisions sur b6 (orchestrateur, 2026-10-10)

  • Pas de 0,5 : décision de l'utilisateur pendant le PIE (« 0.5 »), accordée par Big Boss dans le même BUILD-GO ; écart à STF-T5-SPEC § 2 consigné ici et dans STF-T5-IMPL § 6.
  • Commit de (b) avant la preuve en jeu : décision de l'utilisateur (« Maintenant, comme (a) ») ; J de T5 (a + b) sur la prochaine archive.
  • Commit : Content/UI/CharGen.uasset seul côté contenu (MainMenu déjà commité en 547dcdd).
  • PopulateGenitalSlidersDaz : candidat noté au registre STF, hors STF.