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 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 MainMenu 0 12000` et `… MainMenu_C 0 9000` : FString `ObjectName` suivie de la balise `PropertyName`) : | Tableau | Avant | Après | Disparue | |---|---|---|---| | éditeur (`MainMenu`) | **19** | **18** | l'entrée `SpinBox_0` (`Value <- Get_SpinBox_0_Value_0`, à +8022, 562 o) | | exécution (`MainMenu_C`) | **19** | **18** | idem (à +5825, 403 o) | 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 ` : | | 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 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 | Bin 5046138 -> 5054038 bytes`, 1 fichier | | `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 | Explication | |---|---|---| | `EdGraph` `EventGraph` | 5445 -> 5461 (+16) | 4 nœuds ajoutés x 4 o | | `Function` `ExecuteUbergraph_CharGen` | 102 920 -> 103 201 (+281) | code d'exécution (non décodé) | | `K2Node_VariableGet_280` | 769 -> 793 (+24) | 1 nouveau lien (`CallFunction_59`) | | `K2Node_VariableGet_281` | 854 -> 878 (+24) | 1 nouveau lien (`CallFunction_93`) | | `SpinBox` `SpinBox_0` (`CharGen` et `CharGen_C`, 2 exports) | 171 -> 254 (+83 chacun) | 3 propriétés ajoutées : `Delta` (29) + `bAlwaysUsesDeltaSnap` (25) + `MaxFractionalDigits` (29) = 83 : voir C2b | **É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.