Fiche STM1-SPEC-01 — Niveau N2 — Modèle prévu opus medium | utilisé claude-opus-5-5 — Agent session « Medium · STM1 » (pas de sous-agent) — Skills sk-protocol, lp-ue-compat (lus en CART-01) # STM1-SPEC-01 : trace en jeu du choix de la ville (F3, option O1) (2026-10-20) Ordre : Big Boss (`local_104ff22d`), 2026-10-20. Décision de l'utilisateur citée par Big Boss (session Big Boss, 04:30) : « pour street map on prépare la trace en jeu pour vérifier le pb puis on fournis une buil de développment pour les fixes en cours. La build sera générée une fois que l'on auras complété toutes les opérations ». Contrainte de Big Boss : `GI.uasset` ne sera révisé qu'une fois, en B2 (décision CG2 de 04:00) ; trace hors GI de préférence. SPEC rédigée sans copie UE ni MCP ; aucun fichier du projet modifié. **Résultat : SPEC prête, trace entièrement hors GI.** Elle tient en **C++ du module `LifePlay`** : un sous-système neuf qui lit le GI par réflexion, sans l'écrire, et une ligne de journal ajoutée dans `LPAssetRegistryHelpers`. Elle s'appuie aussi sur les `PrintString` déjà présents dans `MoveCity`, qui écrivent au journal en Development. Aucun asset touché, ni Blueprint ni plugin StreetMap. **Aucune lecture MCP préalable n'est nécessaire.** ## 1. Ce que la trace doit trancher D'après STM1-CART-02 § 1 (M1-M5), la carte tombe sur `000Dummy` par l'un de ces chemins. Chacun doit laisser une signature distincte : | Chemin | Signature attendue dans la trace | |---|---| | C1 : démarrage resté sur la ville factice (`AutoLoad(Dummy=true)`, pas de `MoveCity` suivant) | `POSE … Static/000Dummy` sans `DEMANDE` d'une autre ville ensuite | | C2 : nom de ville sauvegardé introuvable (`GetCityByName` vide) | `SAUVEGARDE ville='X' … AUCUNE correspondance`, puis `RESOLUTION '/Game/Heavy/StreetMap/' … ECHEC (chemin par defaut de City)` | | C3 : chemin résolu en échec, puis repli sur `Cities[0]` | `RESOLUTION '' ECHEC`, puis `LogBlueprintUserMessages … NOT VALID`, puis `POSE` de `Cities[0]` | | C4 : autre cause (ville bien demandée et trouvée, mais mauvais maillage posé) | `DEMANDE` et `RESOLUTION OK` de la bonne ville, mais `POSE` d'un autre maillage | ## 2. Forme - **Journal seulement**, rien à l'écran. Catégorie dédiée **`LogLPCity`**, verbosité `Log`, préfixe `[LPCity]` et une ligne par événement. Visible dans `Saved/Logs/LifePlay.log` en Development et en PIE. Absente du Shipping : corps sous `#if !UE_BUILD_SHIPPING`, et journalisation désactivée en Shipping par défaut. - **Sur changement seulement** : chaque valeur suivie n'est écrite que quand elle change. Le coût reste négligeable : une lecture par réflexion toutes les 0,25 s. - Toujours active en Development, sans option de ligne de commande. Une commande console `LP.CityTrace` écrit l'état courant à la demande. - Les `PrintString` / `PrintText` existants de `MoveCity` (nom de ville, `NOT VALID`, `Cast To Static Mesh Succeeds`) sont déjà écrits dans `LogBlueprintUserMessages` en Development. Ils s'affichent aussi à l'écran, comme aujourd'hui : on ne les modifie pas (ils sont dans le GI). Preuve : `UE_5.8/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp`, `PrintString`, garde `#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) || USE_LOGGING_IN_SHIPPING` ; nœuds : `etat/ST/STB/dumps/GI__EventGraph.txt:17775` (`'NOT VALID'`, `bPrintToLog = 'true'`), `etat/ST/STE/dumps/GI__EventGraph_P2.txt:16510-16570`. ## 3. Points de mesure et emplacement ### T1. Résolution des chemins de ville : `Source/LifePlay/LPAssetRegistryHelpers.cpp` (modification) `MoveCity` résout `StreetMapName` et `StaticMeshName` par `ULPAssetRegistryHelpers::GetAssetByObjectPath` (CART-02 M2, `GI__EventGraph_P2.txt:12245-12275`). C'est le seul passage obligé de chaque changement de ville : on le trace ici. - Si `ObjectPath` commence par `/Game/Heavy/StreetMap` : **à chaque appel**, sans le dédoublonnage actuel (qui ne garde que le premier échec), `UE_LOG(LogLPCity, Log, TEXT("[LPCity] RESOLUTION '%s' -> %s%s"), Chemin, OK|ECHEC, Etiquette)` avec : - `Etiquette` = ` (chemin par defaut de City : ville non trouvee)` si le chemin finit par `/` (`/Game/Heavy/StreetMap/` ou `.../Static/`, valeurs par défaut de la structure `City`, CART-02 M1) ; - ` (ville factice)` si le chemin contient `000Dummy` ; - vide sinon. - Les autres chemins (corps, vêtements, coiffures) gardent le comportement actuel, sans changement. - L'avertissement `GetAssetByObjectPath ECHEC` existant (`LPAssetRegistryHelpers.cpp:25-36`) reste tel quel. ### T2. Sous-système `ULPCityTraceSubsystem` : `Source/LifePlay/LPCityTraceSubsystem.h` / `.cpp` (nouveaux) Un `UGameInstanceSubsystem`, sur le modèle de `LPDebugSubsystem` et `LPDirectControlSubsystem`. Ticker `FTSTicker` à 0,25 s créé dans `Initialize` et retiré dans `Deinitialize`. Lecture par réflexion comme `DirectActor` (`LPDirectControlSubsystem.cpp:148-152`) et `FScriptArrayHelper` (`:217`). **Lecture seule** : aucune écriture dans le GI, aucun appel de fonction Blueprint. | Id | Valeur lue | Chemin de réflexion (noms établis par les dumps) | Ligne écrite (au changement) | |---|---|---|---| | T2a | Liste des villes | `GI.Cities` (tableau de `City`) | une fois, dès que la liste n'est plus vide : `[LPCity] CITIES n= [0]='' sm='' mesh='' ; CentralLondon a l'indice ` | | T2b | Ville sauvegardée | `GI.SaveGameInstance` → propriété `City` (lue par `ExportTextItem`, quel que soit son type) | `[LPCity] SAUVEGARDE ville='' -> trouvee indice ('')` ou `-> AUCUNE correspondance (GetCityByName rendra la City par defaut)` | | T2c | Ville demandée | `GI.CityToMoveTo` (struct `City`) | `[LPCity] DEMANDE '' sm='' mesh=''` | | T2d | Données StreetMap posées | `GI.StreetMapStatic` → propriété `StreetMap` (objet `UStreetMap`) | `[LPCity] STREETMAP ` | | T2e | Maillage affiché | `GI.StreetMapStatic` → propriété `StaticMeshComponent` → `GetStaticMesh()` | `[LPCity] POSE (t=)` | Règles d'écriture : - Les membres de `City` portent un suffixe de GUID (`DisplayName_2_EBDEAF…`, `StreetMapName_26_C43E02…`, `StaticMeshName_28_61293D…`, `etat/portage55/L6f/L6f_dump_GI.txt:25637-25647`). On les cherche **par préfixe** (`DisplayName_`, `StreetMapName_`, `StaticMeshName_`), pas par nom complet. - T2b reproduit `GetCityByName` (`L6f_dump_GI.txt:25623-25660`) : comparaison **insensible à la casse** (`ESearchCase::IgnoreCase`) entre la ville sauvegardée et `DisplayName` converti en chaîne. On ne fait que la constater ; le GI garde sa propre logique. - Propriété introuvable (le GI a pu changer depuis les dumps du 9-10 octobre) : une ligne `[LPCity] PROPRIETE ABSENTE .` **une seule fois**, puis on saute ce point de mesure. La trace se signale elle-même, sans plantage. - Commande console `LP.CityTrace` : écrit d'un coup T2a à T2e, plus `GI.Cities.Num()`. ### T3. Catégorie de journal : `Source/LifePlay/LifePlay.h` / `LifePlay.cpp` (modification) `LIFEPLAY_API DECLARE_LOG_CATEGORY_EXTERN(LogLPCity, Log, All);` à côté de `LogLifePlay` (`LifePlay.h:9`), et `DEFINE_LOG_CATEGORY(LogLPCity);` dans `LifePlay.cpp`. Une seule définition : pas de `DEFINE_LOG_CATEGORY_STATIC` répété, qui entrerait en conflit dans un même fichier unity. ## 4. Pourquoi hors GI, et l'option GI chiffrée | Option | Ce qu'elle voit | Coût | Risque | |---|---|---|---| | **S1 (retenue par cette SPEC) : C++ (T1 + T2 + T3)** | toutes les signatures C1-C4 : ville sauvegardée, correspondance, ville demandée, chemins résolus, maillage posé | 2 fichiers neufs (~200 lignes), 3 fichiers modifiés (~25 lignes), une compilation de `LifePlay` | faible : lecture seule, sans asset ; se signale si un nom de propriété manque | | S2 : plugin StreetMap | rien d'utile : le plugin ne connaît ni le GI ni le `StaticMeshComponent` de la carte (CART-01 § 1) | — | — | | S3 : nœuds `PrintString` dans le GI (`AutoLoad` : valeur de `Dummy` ; `GetCityByName` : sortie trouvée ou vide ; `MoveCity` : branches) | en plus de S1 : la valeur exacte de `Dummy` et la branche prise sans déduction | ~8-12 nœuds par MCP (`lp-mcp-bp`), sauvegarde du GI (52,7 Mo), `CompileAllBlueprints`, VAL ; **seulement dans la révision unique B2**, sinon une seconde révision du GI | moyen : touche le gros Blueprint partagé ; inutile si S1 suffit | | S4 : `PC.uasset` (`BeginAutoLoad(Dummy)` est un événement du PC, `etat/ST/STE/dumps/PC__EventGraph.txt:2762-2766`) | la valeur de `Dummy` au démarrage | 1-2 nœuds, sauvegarde du PC, VAL | faible à moyen ; seulement si S1 laisse C1 ambigu | Les crochets du moteur sur l'exécution Blueprint (`FBlueprintCoreDelegates::OnScriptProfilingEvent`, `UE_5.8/.../CoreUObject/Public/UObject/Script.h:455`) exigent des Blueprints instrumentés (profilage éditeur) : ils ne sont pas fiables dans une build cuite (estimé), donc écartés. Le GI dérive de `/Script/Engine.GameInstance` et non d'une classe C++ du projet (`grep -a` sur `Content/BPs/GI.uasset`) : on ne peut pas surcharger `ProcessEvent` côté projet. ## 5. Lecture MCP préalable : non nécessaire Les noms utilisés (`Cities`, `CityToMoveTo`, `StreetMapStatic`, `SaveGameInstance`, `City`, `StreetMap`, `StaticMeshComponent`, préfixes des membres de `City`) sont établis par les dumps STB, STE et L6f. S'ils ont changé (CG2, B2), la trace écrit `PROPRIETE ABSENTE` au lieu de mentir. La lecture MCP de CART-02 § 4 reste utile pour **interpréter** le résultat (ordre de `Cities`), pas pour poser la trace. ## 6. Déroulé proposé (lp-lot), sous le jeton UE quand il sera libre 1. IMPL (`lp-impl`, N3) : T3, T1, T2 sous instantané `Tools/Snapshot.ps1`, entrée CHG, aucun asset. 2. BUILD (`lp-builder`) : `Tools/Build.ps1`, éditeur Development ; 0 erreur, 0 avertissement nouveau. 3. VAL (`lp-validator`) : en PIE ou par un commandlet de démarrage, puis `grep "\[LPCity\]" Saved/Logs/LifePlay.log`. Attendu au minimum : `CITIES n=…` (au moins une ligne) et `POSE …/000Dummy` au démarrage ; 0 ligne `PROPRIETE ABSENTE` ; rien de nouveau à l'écran ; build Shipping sans symbole `LPCityTrace` (corps exclu). Diff relu : aucune écriture par réflexion. 4. Livraison dans la build Development commune, générée après toutes les opérations en cours (décision de l'utilisateur). 5. L'utilisateur joue jusqu'au symptôme et transmet le journal. Lecture : la signature C1-C4 du § 1 désigne le correctif (SPEC suivante). ## 7. Critères d'acceptation | # | Critère | Preuve attendue | |---|---|---| | A1 | Aucun fichier `Content\` ni `Plugins\` modifié | `git status --short -- Content Plugins` vide | | A2 | Compilation Development sans erreur ni avertissement nouveau | résumé `Tools/Build.ps1` | | A3 | Lignes `[LPCity]` présentes en Development : `CITIES` et `POSE` au minimum | `grep -c "\[LPCity\]"` ≥ 2 sur un journal PIE | | A4 | Rien de nouveau à l'écran | essai visuel ou absence d'appel à `AddOnScreenDebugMessage` dans le diff | | A5 | Absent du Shipping | `#if !UE_BUILD_SHIPPING` autour du corps, relu dans le diff | | A6 | Lecture seule du GI | diff : seulement `GetObjectPropertyValue`, `ContainerPtrToValuePtr` en lecture, `ExportTextItem` | ## 8. Incertitudes, écarts, fichiers - Incertain : le type exact de `Save.City` (chaîne ou texte). `ExportTextItem` couvre les deux. Il reste aussi à confirmer que `CityToMoveTo` est bien posé à chaque `MoveCity` (`VariableSet_4974`, `GI__EventGraph_P2.txt:12289-12294`, sur deux branches seulement). T1 et T2e couvrent le cas où T2c manque un changement. - Non mesuré : rien (SPEC). Aucun fichier du projet modifié. - Fichiers écrits : `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\REL\STM1\STM1-SPEC-01.md` (A) ; `STM1-TACHES.md` (M). Tours : ≈ 9 / 40 (déclaré ; `FicheStats.py` fait foi). À verser : `lp-mcp` ou `lp-commandlets` — en Development, les `PrintString`/`PrintText` des Blueprints écrivent dans `LogBlueprintUserMessages` (garde `!(SHIPPING||TEST)`) : un graphe déjà bavard se trace par le journal, sans toucher au Blueprint. Suite : Big Boss doit ouvrir l'IMPL STM1 (T1-T3, C++ seul) dans la file du jeton UE, avant la build Development commune. État : SPEC close, aucun fichier du projet modifié.