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

364 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 | 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 <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 | 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.