LifePlay-Revival/Docs/Migration/etat/REL/MAPDL1/MAPDL1-REX-2026-10-22.md

159 lines
16 KiB
Markdown
Raw Normal View History

Fiche MAPDL1-REX-2026-10-22 — Niveau N2 — Modèle prévu opus medium | utilisé claude-opus-5-5 — Agent session « Medium · MAPDL1 » — Skills sk-protocol, lp-lot, lp-city
# MAPDL1 : retour d'expérience pour les skills et les agents (2026-10-22)
Demande de l'utilisateur, citée mot pour mot par Big Boss : « je souhaite aussi leur retour d'expérience pour enrichir les skills et agents ».
Cette fiche ne touche **ni les skills ni les agents** : Big Boss les posera après l'accord de l'utilisateur.
Chaque leçon donne quatre choses :
- **Fait** : ce qui a été observé, avec sa preuve (fichier:ligne, ou livrable du lot) ;
- **Règle** : ce qu'il faut retenir ;
- **Où** : le skill et sa section, ou l'agent ;
- **Texte** : un texte prêt à coller.
Priorités :
- **P1** : évite une perte de données, une build cassée ou une donnée fausse ;
- **P2** : évite des tours ou des allers-retours ;
- **P3** : confort.
## P1
### R1. Le `Move` du moteur supprime la destination
- **Fait** : `IFileManager::Move(Dest, Src, Replace=true, …)` supprime d'abord la destination (`Engine/Source/Runtime/Core/Private/HAL/FileManagerGeneric.cpp:303`). E5 a donc choisi `Move(…, false, false, false, true)` (`LPCityMapsDownload.cpp:109`) pour ne jamais rien supprimer. Vu en relecture, avant tout dégât.
- **Règle** : dans un code qui « ne supprime jamais », `Replace` est toujours `false`. On écarte d'abord la destination existante par un déplacement horodaté.
- **Où** : `lp-ue-compat`, nouvelle section « API de fichiers » ; rappel dans `sk-change-safety`.
- **Texte** : « `IFileManager::Move` avec `bReplace=true` supprime la destination avant de déplacer (`FileManagerGeneric.cpp:303`). Pour une pose sans suppression : `Move(Dest, Src, /*Replace*/false, /*EvenIfReadOnly*/false, /*Attributes*/false, /*bDoNotRetryOrError*/true)`, après avoir déplacé l'ancienne destination dans un dossier horodaté. Source : MAPDL1-E5. »
### R2. OpenSSL dans un module du jeu : conflit de `UI`
- **Fait** : E2 bis a échoué sur une erreur unique, `openssl/ossl_typ.h(144,22): error C2365: 'UI' : redéfinition ; la précédente définition était 'espace de noms'` (`MAPDL1-E2B-BUILD.md`). La correction est la parade du moteur, `#define UI UI_ST` avant les en-têtes et `#undef UI` après (`BuildPatchServices/Private/Common/Crypto.cpp:6-12`). E2 ter a ensuite rendu code 0.
- **Règle** : on n'inclut jamais `openssl/*.h` sans cet encadrement. Avant de lancer la build, on cherche par Grep tous les fichiers qui incluent OpenSSL : une seule build doit suffire.
- **Où** : `lp-ue-compat` (ruptures connues) et `ue-cpp-build` (C2365).
- **Texte** : « OpenSSL : `#define UI UI_ST` / `THIRD_PARTY_INCLUDES_START` / `#include "openssl/evp.h"` / `THIRD_PARTY_INCLUDES_END` / `#undef UI`. Sans cela : C2365 `'UI'` (le typedef d'OpenSSL heurte l'espace de noms `UI` d'UE). Modèle : `Engine/Source/Runtime/Online/BuildPatchServices/Private/Common/Crypto.cpp:6-12`. »
### R3. `Height` d'un UStreetMap est en centimètres
- **Fait** : `StreetMap.h:304` annonce des « meters », mais l'import écrit des centimètres (`StreetMapFactory.cpp:387`). Le GeoJSON de S7 en tient compte : `height_m = Height / 100` (`MAPDL1-S7-IMPL.md` § 2).
- **Règle** : toute lecture de `FStreetMapBuilding::Height` passe en centimètres, quel que soit le commentaire.
- **Où** : `lp-city` § 2, tableau des pièges de géométrie.
- **Texte** : « `FStreetMapBuilding::Height` est en **cm**, malgré le commentaire « meters » (`Public/StreetMap.h:304` ; écrit en cm par `StreetMapImporting/StreetMapFactory.cpp:387`) | établi, MAPDL1-S7 ».
### R4. Une session compile le code posé par une autre
- **Faits** :
- La build de l'éditeur de COM31 a rendu `Target is up to date`, avec 0 unité. La ligne R-1 avait en effet déjà été compilée à 18:23 par la build de la 1.7.4 de Paris : la DLL de l'éditeur était plus récente que la source (`MAPDL1-COM31-BUILD.md`). `lp-builder` y a vu un « échec probable » et s'est arrêté.
- W1 et W2, posés par MAPDL1 sans être compilés, ont été compilés par le build de la 1.7.7 de STM1 (`STM1-155`, code 0).
- MAPDL1 et STM1 se sont échangé leurs listes de fichiers : aucun recouvrement.
- **Règles** :
- (a) Avant d'appeler « échec » un `Target is up to date`, on compare la date de la DLL ou de l'exe à celle de la dernière source modifiée.
- (b) Tout code posé non compilé est annoncé aux autres sessions, avec ses MD5 : leur build le compilera, et une casse doit revenir à son auteur.
- (c) Avant d'écrire dans `Source\`, chaque session publie sa liste de fichiers et attend le signal de Big Boss.
- **Où** : `lp-build` § 8 pour (a) ; `lp-lot/boss.md` pour (b) et (c) ; agent `lp-builder` pour (a).
- **Texte (lp-builder)** : « `Target is up to date` avec 0 unité n'est pas un échec si la DLL ou l'exe est plus récent que la source modifiée : une autre session a déjà compilé. Relève les deux dates (Get-Item), rapporte, et continue la fiche si elle le prévoit. »
- **Texte (boss.md)** : « Code posé non compilé : son auteur l'annonce à Big Boss et aux sessions qui compilent (fichiers et MD5). La première build qui le compile rapporte toute erreur à l'auteur. Écriture dans `Source\` : liste des fichiers échangée entre les sessions, puis signal de Big Boss. »
### R5. Reprise à l'identique d'une ancienne carte UE4
- **Faits** :
- Les cartes « NEW Maps » de Mega sont des UStreetMap UE4 cuits, non versionnés (legacy −7, ue4 = 0, drapeaux 0x80000000), avec des propriétés étiquetées. On les lit hors moteur (`LireCarteUE4.py`) et on les reprend par réflexion (`LPImportLegacyStreetMap`, `MAPDL1-P1-IMPL`).
- Le maillage `LPBuildCityStatic -Flat` passe la comparaison avec Londres (1,59 %). Le réglage par défaut et `-Unlit` échouent (58 %).
- `-Unlit` réduit le nombre de sommets, pas celui des triangles.
- Formule des triangles classiques : `3·Pb − 2·B`, écart observé de 0 à −0,18 %.
- Poids de la 3D classique : environ 20 octets par triangle dans l'archive (`MAPDL1-AUTO-FIN` § 2).
- Un asset pilote laissé sous `Content\` a cassé la cuisson des packs (`DirectoriesToAlwaysCook` couvre `/Game`).
- **Règles** :
- reprise par réflexion, sans conversion ;
- `-Flat` pour le maillage statique repris ;
- aucun asset de travail sous `Content\` entre deux sessions ;
- fourchettes de contrôle tirées de la formule.
- **Où** : `lp-city`, nouvelle section « 9. Reprise d'une ancienne carte LifePlay (Mega) » ; `lp-commandlets`, table des cartes, avec une ligne `LPImportLegacyStreetMap`.
- **Texte (lp-city § 9)** : « Ancienne carte Mega = UStreetMap UE4 cuit, non versionné (legacy −7, ue4 = 0, drapeaux 0x80000000), propriétés étiquetées. Lecture hors moteur : `Tools\LireCarteUE4.py --totaux` ; reprise : `LPImportLegacyStreetMap` (réflexion, `-DryRun`) ; chaîne : `RepriseCarte.ps1` (Lire, Creer, Importer, Classic, ODbL). Maillage statique : `-Flat` (Londres 1,59 % ; défaut et `-Unlit` 58 %). Triangles classiques ≈ `3·Pb − 2·B` (écart de 0 à −0,18 %) ; environ 20 o par triangle dans l'archive. Jamais d'asset de travail sous `Content\` (cuisson cassée, MAPDL1-P1). Les données (sans lat/lon ni identifiants OSM) se livrent en GeoJSON en plan local (SPEC-07). »
## P2
### R6. Exécutants haiku et chemins sous Git Bash
- **Faits** :
- EXEC-1 s'est arrêté sur un code 64 : le chemin du script, qui contient une espace, n'était pas entre guillemets dans Git Bash (`MAPDL1-AUTO-EXEC-1.md`).
- Avec un chemin absolu entre guillemets et des barres obliques, le lot 1 est passé (5 villes sur 5) sans aucune décision à prendre (`AUTO-FIN` § 2).
- `Snapshot.ps1` ne pose pas de code de sortie : il faut `$global:LASTEXITCODE = 0` avant l'appel.
- Un `if` à un seul élément devient un scalaire : il faut `@(if …)` (`JOURNAL-BROUILLON`, 14:15).
- **Règles** :
- une brève pour haiku donne chaque commande **en entier**, avec un chemin absolu entre guillemets et des `/` ;
- elle ne laisse aucun choix ;
- elle donne le code attendu et l'arrêt sur tout écart.
- **Où** : `sk-windows-shell` ; agent `lp-builder` ; `sk-orchestrate` (fiche pour un modèle réduit).
- **Texte (sk-windows-shell)** : « Git Bash et chemin avec espace : `pwsh -NoProfile -File "D:/Game/LifePlay-master 5.8/Tools/X.ps1"` (guillemets, `/`). Sans guillemets : code 64. `Tools\Snapshot.ps1` ne pose pas de code : `$global:LASTEXITCODE = 0` avant l'appel. PowerShell : un `if` qui rend un seul élément est un scalaire ; écrire `@(if …)`. »
### R7. Dépassement du budget de tours (S7 : 62 pour 40)
- **Fait** : `lp-impl` (sonnet) a utilisé 62 tours pour 40 (`MAPDL1-S7-IMPL.md`). Causes déclarées :
- débogage de commandes inline ;
- l'unité de `Height` ;
- des essais ajoutés hors de la fiche (un vrai passage d'ODbL en plus du `-ABlanc` demandé).
La fiche couvrait 3 outils liés physiquement, l'écriture en place et 3 villes d'essai, au niveau N3 avec 40 tours.
- **Règles** :
- une fiche qui touche 3 outils avec des essais mesurés reçoit 60 tours ;
- les commandes répétées passent par un script ponctuel `Saved\<lot>\` dès le 2e essai, pas en inline ;
- aucun essai hors de la fiche sans le noter comme écart **avant** de le lancer.
- **Où** : `sk-orchestrate` (barème du budget) ; agent `lp-impl` ; `sk-protocol` § 8.
- **Texte (lp-impl)** : « Une commande longue ou répétée (pwsh inline, chemins avec `\`) passe par un script ponctuel `Saved\<lot>\` dès le 2e essai. Un essai que la fiche ne demande pas ne se lance pas ; on le propose dans le compte rendu. »
- **Texte (sk-orchestrate)** : « Budget : 3 fichiers ou plus, plus des essais mesurés = 60 tours (N3), pas 40. »
### R8. Mise en page Slate : contrôle obligatoire hors de la zone qui défile ; habillage des chemins
- **Faits** :
- Dans COM31, la case d'accord était dans la même `SScrollBox` (plafonnée par `SBox::MaxDesiredHeight`) que la licence dépliée : elle passait sous le cadre (cas B2, `TESTS-02` § E).
- `AutoWrapText` ne coupe qu'entre deux mots : un chemin sans espace dépasse (B8).
- Correctifs W1 :
- la case et les messages sortent de la zone qui défile ;
- `.WrappingPolicy(ETextWrappingPolicy::AllowPerCharacterWrapping)` (`STextBlock.h:121`, `TextLayout.h:72`) ;
- la hauteur de la zone suit le viewport (`UWidgetLayoutLibrary::GetViewportScale`).
- **Règles** :
- les contrôles obligatoires (case d'accord, boutons) et les messages ne vont jamais dans une zone qui défile ;
- tout texte qui peut contenir un chemin ou une URL reçoit `AllowPerCharacterWrapping` ;
- une fenêtre se vérifie en 1280×720.
- **Où** : agent `lp-impl` (rubrique Slate et UMG) ; `lp-ue-compat` (API Slate 5.8).
- **Texte** : « Slate : les contrôles obligatoires et les messages hors de la `SScrollBox` (plafonnée par `SBox::MaxDesiredHeight`, qui peut suivre le viewport : `MaxDesiredHeight_Lambda` + `UWidgetLayoutLibrary::GetViewportScale`). Chemin ou URL : `.WrappingPolicy(ETextWrappingPolicy::AllowPerCharacterWrapping)`, car `AutoWrapText` ne coupe qu'entre deux mots. Vérifier en 1280×720. Source : MAPDL1, essai COM31 (B2, B8). »
### R9. Fichier partiel vide après un échec réseau
- **Fait** : `CreateFileWriter(…, FILEWRITE_Append)` crée le fichier, et son dossier, avant le premier octet. L'échec voulu de B5 (DNS) a laissé `Saved\Downloads\lpm_city_manila\…zip.part` de 0 octet dans un dossier visible (B9). Correctif W2 :
- le `.part` est écrit à côté du zip ;
- à l'échec et à l'annulation, un `.part` vide est déplacé dans `_rejet\` (`SetAsideEmptyPart`), sans aucun `Delete`.
- **Règle** : un téléchargement qui échoue sans recevoir d'octet ne laisse ni fichier ni dossier visible. On le range (déplacement), on ne le supprime pas.
- **Où** : agent `lp-impl` ; `lp-ue-compat` (HTTP).
- **Texte** : « HTTP en flux (`SetResponseBodyReceiveStream`) : le `.part` existe dès l'ouverture. À l'échec ou à l'annulation, s'il fait 0 octet, le déplacer dans `_rejet\` ; pas de sous-dossier par téléchargement. Source : MAPDL1 W2. »
### R10. Le cahier d'essai se calcule comme le code
- **Fait** : le cahier attendait « Download (21 MB) » pour 22 075 120 octets. Le jeu affiche 22, car il arrondit au-dessus (`LPCityMapsSubsystem.cpp:734`). C'est le cahier qui avait tort (`TESTS-02` A1).
- **Règle** : une valeur attendue se calcule par la formule du code, qu'on cite, et non de tête.
- **Où** : `lp-doc` (gabarit FIT) ; agent `lp-writer`.
- **Texte** : « Valeur attendue d'un affichage : la calculer par la formule du code, avec fichier:ligne (arrondi, unités). Exemple : 22 075 120 o → « 22 MB » (Mo arrondi au-dessus). »
## P3
### R11. Crochet `ecriture-gardien` et agents de lecture
- **Fait observé dans MAPDL1** : une commande Bash de `lp-validator` (E3 bis) a été refusée par le crochet, à cause d'un accent grave dans le texte. C'était un faux positif, sans effet ; l'agent l'a relancée sans ce caractère (`MAPDL1-E3B-VAL.md`, compte rendu).
- **Non observé dans MAPDL1** : le blocage des agents de **collecte** que cite Big Boss. Big Boss le complète avec ses propres preuves.
- **Règle** : pour un agent sans Edit, pas d'accent grave ni de `>` dans les commandes du shell. Les recherches passent par Grep ou Read.
- **Où** : `sk-protocol` § 7 (agents sans Edit) ; `hooks\cas-v2.jsonl` (cas d'essai).
- **Texte** : « Agent sans Edit : aucune commande shell avec accent grave, `>` ni `->`, même dans du texte cité ; le crochet `ecriture-gardien` les refuse (faux positif, MAPDL1-E3B). Reformuler, ou passer par Grep et Read. »
### R12. Outils liés physiquement : écriture en place
- **Fait** : `IndexCartes`, `LireCarteUE4`, `RepriseCarte` et `RecetteCarte` sont des liens physiques entre le jeu et l'addon. Ils ont été écrits par `[IO.File]::WriteAllBytes`, qui tronque le même fichier. Les MD5 sont identiques des deux côtés et `fsutil` montre 2 liens (`S6-1-IMPL`, `S7-IMPL`). La mémoire du projet le disait déjà ; les fiches l'ont rappelé à chaque fois.
- **Règle** : la règle est déjà en mémoire ; il faut l'écrire dans le skill.
- **Où** : `lp-paths` (Outils du jeu) ; agents `lp-impl` et `lp-writer`.
- **Texte** : « Beaucoup de fichiers de `Tools\` sont liés physiquement à l'addon : Edit et Write rompent le lien. Vérifier par `fsutil hardlink list` ; écrire en place par `[IO.File]::WriteAllBytes(<Tools>, [IO.File]::ReadAllBytes(<brouillon>))` ; relire les 2 liens et les 2 MD5. »
### R13. `EtatDir.ps1` ne connaît pas un nouveau lot
- **Fait** : `Build.ps1` a écrit ses résumés dans `etat\MAPDL1-E2B\`, `etat\MAPDL1-E2T\` et `etat\MAPDL1-COM31\`, à la racine d'`etat\`. Ils ont été déplacés à la clôture (`MAPDL1-BILAN` § 6).
- **Règle** : à l'ouverture d'un lot, on ajoute sa règle à `EtatDir.ps1`, ou on passe `-EtatDir` à chaque `Build.ps1` et à chaque `RunCommandlet.ps1`.
- **Où** : `lp-lot` (Avant toute modification) ; agent `lp-builder` (passer `-EtatDir` si la fiche le donne).
- **Texte** : « Nouveau lot : règle dans `Tools\EtatDir.ps1`, ou `-EtatDir <etat>\<projet>\<lot>` sur chaque `Build.ps1` / `RunCommandlet.ps1`. Sinon, les résumés vont à la racine d'`etat\` (MAPDL1). »
### R14. Avertissement préexistant à séparer d'un effet nouveau
- **Fait** : `Accessed None … property GI in /Game/UI/CityMenu.CityMenu_C` apparaît 5 fois dans l'essai COM31. Il figure déjà dans les 6 derniers journaux de COM30, sans E1 (`TESTS-02` § E).
- **Règle** : avant d'attribuer un avertissement à un lot, on le compte dans les journaux de la build précédente.
- **Où** : `lp-validate` § 3 ; agent `lp-validator`.
- **Texte** : « Avertissement en jeu : le compter dans les journaux de la build d'avant (`Saved\Logs\*.log`) avant de l'attribuer au lot. Exemple : CityMenu `GI` (Accessed None), préexistant à MAPDL1. »
## Fichiers
- A `Docs/Migration/etat/REL/MAPDL1/MAPDL1-REX-2026-10-22.md`
- Aucun skill ni agent n'est modifié.