LifePlay-Revival/Docs/Migration/etat/EVAL2/AMEL1/OUTIL1/OUTIL1-D2-IMPL.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

33 KiB

Fiche OUTIL1-D2 — Niveau N3 — Modèle prévu sonnet | utilisé claude-sonnet-5-5 — Agent lp-impl — Skills sk-protocol, ue-cpp-build, lp-build, lp-ue-compat

OUTIL1-D2-IMPL : fusion de F5, DESTROY et hitch de logstats.py dans Tools\LogStats.ps1 (2026-10-14)

Hors gabarit : les sections 7 à 11 s'ajoutent aux six sections IMPL, à la demande de la fiche (MD5 et diff, non-régression collée, PubCheck, ligne d'index). La section 11 rend compte de la reprise OUTIL1-D2-b (trame calculée seulement avec -Stats) et prévaut sur les sections 1, 5, 7 et 8 là où elles diffèrent : celles-ci décrivent l'état intermédiaire (MD5 FB7C7143E70F1CC0242C98EDD8B0FE86, trame toujours calculée).

1. Fichiers modifiés (chemins, lignes)

Un seul fichier de code : D:\Game\LifePlay-master 5.8\Tools\LogStats.ps1, 141 → 289 lignes (état final, reprise D2-b), 148 ajoutées, 0 retirée par rapport à l'original (git diff --no-index --numstat : 148 0 ; état intermédiaire : 286 lignes, 145 0). Aucune ligne de l'original n'est touchée. Depuis la reprise D2-b, les numéros de ligne du tableau ci-dessous sont décalés de +1 à partir de la ligne 15 (l'aide gagne une ligne).

Lignes Ajout Source dans logstats.py (ST\STE\STE-J6-TRACE\logstats.py)
10-14 aide .DESCRIPTION : décrit la nouvelle section et rappelle que le corps s'exécute à la lecture sans objet (texte)
79 $rxTramePrefixe : horodatage et numéro de trame RX_PREFIX, l. 5
80 $rxTrameF5 : « No SkeletalMesh for Component(CharacterMesh0) » par socket et acteur RX_F5, l. 6
81 $rxTrameDestroy : DESTROY.*index Person_C_n RX_DESTROY, l. 7
82 $rxTrameHitch : LPDebug hitch : <ms> ms RX_HITCH, l. 11
84-88 Get-LPCompte (compteur d'un dictionnaire, 0 si absent, comme Counter) collections.Counter, l. 58
90-96 Get-LPGroupesF5 : min(Head // 2, pelvis, chestUpper, hip) l. 61 et l. 67
98-100 Format-LPDecimale1 : x.ToString('F1') invariant :.1f, l. 84
102 Format-LPListe : [a, b, c] comme le repr d'une liste Python l. 76-77
104-169 Get-LPTrameStats : une boucle sur les lignes, mêmes étapes que la boucle Python trame l. 27-31, F5 l. 34-36 (condition 'LogSkinnedMeshComp' in s incluse, l. 35), DESTROY l. 37-39, hitch l. 50-52, groupes l. 58-62, DESTROY par trame l. 64-78, hitch l. 82-84
171-187 Show-LPTrame : mêmes libellés que print l. 54, 55 (partie F5), 56, 62, 63, 76-78, 84
189-208 Get-LPTrameMesures : une ligne par mesure pour -Compare (plus une ligne F5 socket <nom> par socket) sans équivalent (le Python ne compare pas)
244 (état final) propriété cachée _Trame de Get-LogStats, calculée seulement si -Stats : $(if ($Stats) { Get-LPTrameStats $lines }) (préfixe _ : exclue du Format-List et de la table -Compare, donc sorties anciennes intactes)
252 (état final) Show appelle Show-LPTrame après la table « Acces a None par propriete », si _Trame existe
265-271 (état final) fin de -Compare, dans un if ($Stats) { … } : ligne vide, titre, table Mesure / A / B

Comportement ajouté dans la sortie (état final) : section « Trame, F5, DESTROY, hitch (motifs de logstats.py) » seulement avec -Stats, après les compteurs normalisés (mode un journal) et en fin de -Compare -Stats. Sans -Stats, ainsi qu'en -Pattern et -Tail, la sortie et la durée sont celles de l'original (mesuré § 11).

Corps du script : inchangé, il s'exécute toujours à la lecture (pas de mode bibliothèque ; les fonctions et regex ajoutées sont définies avant le corps, comme $reNone). EssaiJournal.ps1 n'est pas touché et ne peut toujours pas appeler LogStats.ps1 sans l'exécuter.

2. Écarts à la SPEC (obligatoire)

La fiche tient lieu de SPEC. Écarts et choix, un par ligne :

  • E1 — « nouvelles colonnes ou sections » : choix de sections après la sortie existante (propriété cachée _Trame), plutôt que des colonnes dans le Format-List ou la table -Compare : c'est ce qui garde l'ancienne sortie comme préfixe exact de la nouvelle (MD5 égaux, § 8).
  • E2 — Familles de logstats.py non refusionnées car déjà dans LogStats.ps1 : lignes (Lignes), "No SkeletalMesh" (toutes lignes) (NoSkeletalMesh), index SPAWN (Personnages). Mesuré égal sur 3 journaux : lignes 13402 / 17963 / 85697, NoSkeletalMesh 2717 / 1473 / 44635, SPAWN 19 / 11 / 345 des deux côtés (§ 8). Les définitions diffèrent pourtant (RX_SPAWN l. 8 est (Person_C_\d+)\s+index SPAWN, le PS compte index SPAWN sans casse ; -match du PS est insensible à la casse, in du Python ne l'est pas) : égalité constatée, non démontrée.
  • E3 — Famille de logstats.py non fusionnée, hors fiche : « Accessed None par couple (fonction, propriété) » (logstats.py l. 9-10, 42-49, 79-81 ; LogStats.ps1 ne compte que par propriété). À décider (§ 5, P4).
  • E4 — Un LPDebug hitch : <nombre illisible> ms (motif [\d.]+ accepte « 1.2.3 ») fait planter logstats.py (float()), alors que le PS ignore la valeur et l'écrit sur une ligne « valeurs illisibles ignorées » (affichée seulement si > 0). Aucun cas dans les journaux essayés.
  • E5 — sum(hs) du Python ≥ 3.12 est compensé (Neumaier) ; le PS additionne simplement dans l'ordre croissant. Total identique à 0,1 s près sur 3 journaux ; un écart d'arrondi n'est possible qu'à une frontière de .1f.
  • E6 — Pas de code de sortie 0/1/2 : logstats.py lu en entier n'en a aucun (pas de sys.exit) ; la note du CSV (« codes 0/1/2 ») ne vise pas ce script. Interface -Log/position 0 de LogStats.ps1 gardée.
  • E7 — « Avant » = l'instantané exécuté sur place (D:\Game\LifePlay_Backup_5.5\OUTIL1-D2\Tools\LogStats.ps1), sans copie ailleurs ; pour -Latest, -LogDir est passé explicitement aux deux versions (le script d'instantané n'est pas dans Tools\).
  • E8 — Tools\LISEZMOI.md n'existe pas dans la copie 5.8 (Get-ChildItem -Recurse -Filter LISEZ* sous Tools : vide ; Glob **/LISEZMOI.md : aucun). Non créé (la fiche l'interdit) ; ligne proposée § 10.
  • E9 — Entorse à la règle de shell : ma première commande de non-régression se terminait par 2>&1 | head -0; echo (ajouté par erreur). Elle a tourné (aucun refus du crochet), sortie jetée par head -0, lecture seule sur les journaux ; rejouée aussitôt sans cet ajout, c'est celle-là qui est collée en § 8. Aucune autre commande ne contenait >, <, ->, tee, heredoc ou python -c. Autre entorse, à la règle « Grep/Glob/Read plutôt que le shell » de sk-protocol § 7 : un listage ls -la <dossier> | head -80 du dossier de sauvegarde (lecture seule).
  • E10 — Coût en temps : réglé par la décision de l'orchestrateur en reprise D2-b (trame seulement avec -Stats, § 11).

3. Instantané (lot, manifeste)

pwsh -NoProfile -Command "& 'D:\Game\LifePlay-master 5.8\Tools\Snapshot.ps1' -Lot OUTIL1-D2 -Path @('Tools\LogStats.ps1')" avant toute écriture. Sortie :

sauvegarde : Tools\LogStats.ps1 (5CD51FB7F1CFA10CFCC7DD4B2FE3F65D)
1 entree(s) ajoutee(s) a D:\Game\LifePlay_Backup_5.5\OUTIL1-D2\manifest.csv

Copie : D:\Game\LifePlay_Backup_5.5\OUTIL1-D2\Tools\LogStats.ps1. Retour arrière proposé (non lancé) : Tools\Restore.ps1 -Lot OUTIL1-D2.

4. Ce qui n'a pas été fait

  • Famille « Accessed None par (fonction, propriété) » de logstats.py (E3) : reste, décision de l'orchestrateur (non fusionnée pour l'instant) ; mode bibliothèque (interdit par la fiche sans signalement). L'interrupteur de la trame est -Stats (reprise D2-b, § 11).
  • logstats.py non touché, non déplacé : mis à la corbeille seulement sur décision après VAL (la fiche ne le demande pas).
  • Tools\LISEZMOI.md : absent, non créé (E8). Aucun autre outil touché.
  • Pas de compilation (sans objet : script PowerShell). Analyse syntaxique seule : Parser::ParseFile → erreurs de syntaxe : 0.
  • Non mesuré : journaux de la 5.8 (jeu empaqueté) et sortie sous Windows PowerShell 5.1 (le fichier est UTF-8 sans BOM comme avant : 5.1 lisait déjà mal les 2 lignes accentuées de $reNone et IndexHorsBornes).

5. Points à confirmer en VAL (un par ligne)

  • P1 — Rejouer la non-régression de l'ancienne sortie sur au moins deux journaux (§ 8) : ancienne sortie = préfixe exact de la nouvelle, MD5 égaux.
  • P2 — Rejouer la parité F5 / DESTROY / hitch avec python -I -X utf8 logstats.py T <journal> sur COMMUN-10 et COMMUN-13 (9 lignes égales sur chacun).
  • P3 — Réglé en reprise D2-b (décision de l'orchestrateur) : la trame n'est calculée qu'avec -Stats (un journal et -Compare). Constat de la première version (trame toujours calculée) : 0,57 s → 8,31 s sur COMMUN-2 (85 697 lignes, 15,8 Mo), +0,8 s sur 13 000-18 000 lignes ; micro-mesure : boucle du préfixe 1,6 s, trois Contains 0,9 s, boucle F5 avec dictionnaires 3,4 s. État final : durées au § 11.
  • P4 — « Accessed None par (fonction, propriété) » de logstats.py : décision de l'orchestrateur, non fusionnée pour l'instant, à noter en reste (E3).
  • P5 — Vérifier que Tools\EssaiJournal.ps1 et les autres appelants de LogStats.ps1 n'ont pas besoin d'être revus : aucun appelant n'a été lu ni modifié ici.
  • P6 — Fins de ligne et encodage (état final) : LF seul (CR = 0, LF = 289), pas de BOM (octets de tête 60,35,10), 12 octets non ASCII comme avant (§ 11).
  • P7 — PubCheck sur le script et sur ce livrable = 0 (§ 9 et § 11).
  • P8 — Tools\LISEZMOI.md absent : la fiche OUTIL1-A le crée ; la ligne du § 10 lui est transmise par l'orchestrateur.

6. Fichiers à commiter

Dépôt jeu (D:\Game\LifePlay-master 5.8, branche ue5.8) :

  • A Docs/Migration/etat/EVAL2/AMEL1/OUTIL1/OUTIL1-D2-IMPL.md

Tools/LogStats.ps1 est modifié sur le disque seulement : Tools/ est ignoré (git check-ignore -v Tools/LogStats.ps1 → .gitignore:91:/Tools/), rien à commiter pour le script. Fichier de travail hors dépôt : fmt_check.py (dossier temporaire de la session, essai unique du format .1f), à ne pas commiter.

7. MD5 avant / après et diff résumé

Octets Lignes MD5
Avant (instantané) 7578 141 5CD51FB7F1CFA10CFCC7DD4B2FE3F65D
Après, état intermédiaire (avant la reprise D2-b ; copie D:\Game\LifePlay_Backup_5.5\OUTIL1-D2b\Tools\LogStats.ps1) 15841 286 FB7C7143E70F1CC0242C98EDD8B0FE86
Après, état final (reprise D2-b) 16027 289 A821D3C74FFA7A63A15A5754706A6B1E

Commande : [IO.File]::ReadAllBytes et Get-FileHash -Algorithm MD5 (pwsh 7.6.6). Sortie brute de l'état intermédiaire (celle de l'état final est au § 11) :

taille=15841 premiers octets=60,35,10,46
CR=0 LF=286
non-ASCII=12
MD5 apres=FB7C7143E70F1CC0242C98EDD8B0FE86
MD5 avant (instantane)=5CD51FB7F1CFA10CFCC7DD4B2FE3F65D
lignes=286
145	0	D:/Game/{LifePlay_Backup_5.5/OUTIL1-D2 => LifePlay-master 5.8}/Tools/LogStats.ps1

Diff résumé : 5 emplacements d'ajout pur (aide l. 10-14 ; bloc de fonctions l. 78-208 ; _Trame l. 243 ; appel l. 251 ; fin de -Compare l. 264-268). Modifications faites avec l'outil Edit (aucun script de remplacement).

8. Non-régression (collée)

Les blocs 8.2 à 8.5 sont ceux de l'état intermédiaire (trame toujours calculée, MD5 FB7C7143…) ; ils restent exacts pour la parité Python (8.4) et le format (8.1). La non-régression de l'état final, avec et sans -Stats, est au § 11.

Trace : D:\Game\LifePlay-master 5.8\Docs\Migration\etat\EVAL2\AMEL1\OUTIL1\OUTIL1-D2-IMPL.md § 8 — Fiche OUTIL1-D2 — 2026-10-14
Commande     : pwsh -NoProfile -Command '<bloc>' où <bloc> exécute, pour chaque journal $j, l'ancien script
               ($old = D:\Game\LifePlay_Backup_5.5\OUTIL1-D2\Tools\LogStats.ps1) et le nouveau
               ($new = D:\Game\LifePlay-master 5.8\Tools\LogStats.ps1) : @(& $old $j | Out-String -Stream) et
               @(& $new $j | Out-String -Stream), compare $o à $n[0..($o.Count-1)] (Compare-Object -CaseSensitive) et
               leurs MD5 (UTF-8, lignes jointes par LF), puis python -I -X utf8
               "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-J6-TRACE\logstats.py" T $j
               (Python 3.14.4) ; les lignes de la section ajoutée sont comparées une à une à celles du Python
               (la ligne « lignes= » devient « horodatage premier= … ; blocs_trame= », la ligne
               « "No SkeletalMesh" … ; F5 … » devient « F5 LogSkinnedMeshComp=n », « DESTROY=n ; index SPAWN=s »
               devient « DESTROY=n » ; lignes « AN » et « Accessed None » écartées). Texte complet du bloc :
               transcription de la session.
Environnement: copie 5.8, branche ue5.8, Tools\LogStats.ps1 MD5 FB7C7143… ; pwsh 7.6.6 ; Python 3.14.4 ;
               journaux D:\Game\LifePlay_Backup_5.5\COMMUN-10\essais\commun10_LifePlay.log (13402 lignes),
               COMMUN-13\essais\commun13_LifePlay.log (17963), COMMUN-2\essais\LifePlay_commun2_definitif_BOSS.log
               (85697), en lecture seule ; machine non chargée par ailleurs (un seul processus lancé à la fois).
Attendu      : ancienne sortie = préfixe exact de la nouvelle (MD5 égaux) ; 9 lignes de famille nouvelle égales
               à celles du Python ; modes -Pattern/-Tail/-Latest sans -Stats identiques de bout en bout.
Obtenu       : ci-dessous.
Lecture      : le test « modes inchangés » du premier essai était faux (splat d'un tableau = arguments positionnels,
               0 ligne comparée des deux côtés) ; refait avec des tables de hachage. Une fonction nommée H masquée par
               l'alias Get-History a vidé les MD5 du premier essai ; refait avec Md5Texte. Les blocs ci-dessous
               viennent des essais refaits. Le bloc COMMUN-10 / COMMUN-13 / COMMUN-2 « sections ajoutées » vient de l'état
               final du fichier pour COMMUN-2 et -Compare, de l'état précédent (avant retrait d'un contournement
               d'arrondi inutile dans Format-LPDecimale1) pour les sections COMMUN-10 et COMMUN-13 ; les deux
               journaux ont été rejoués dans l'état final (lignes « familles nouvelles = logstats.py : True »).

8.1 Format .1f : Python contre .NET (pwsh 7.6.6), 11 valeurs dont les égalités exactes

--- python format .1f            --- .NET F1 direct
0.25 0.2                         0.25 0.2
0.75 0.8                         0.75 0.8
0.35 0.3                         0.35 0.3
12.05 12.1                       12.05 12.1
0.45 0.5                         0.45 0.5
0.15 0.1                         0.15 0.1
33.25 33.2                       33.25 33.2
1234.75 1234.8                   1234.75 1234.8
2.65 2.6                         2.65 2.6
100.05 100.0                     100.05 100.0
7.95 8.0                         7.95 8.0

(Les deux listes sont les deux sorties du même essai, mises côte à côte ici ; elles sont identiques ligne à ligne. Un premier contournement d'arrondi supposait que .NET arrondit 0.25 vers le haut : faux sur cette version, retiré.)

8.2 Modes existants : sortie entière identique (état final)

=== modes inchanges : sortie entiere identique
Log,Max,Pattern : identique=True lignes=15 MD5 606CE80D352AF052231AC22A9E1D1ED9 / 606CE80D352AF052231AC22A9E1D1ED9
Log,Tail : identique=True lignes=8 MD5 43CCD36CEBA64CF2CA1F75D7D645F12E / 43CCD36CEBA64CF2CA1F75D7D645F12E
Context,Log,Max,Pattern : identique=True lignes=10 MD5 C4F78E532D3D46426AE909AA22887D8B / C4F78E532D3D46426AE909AA22887D8B
Latest,LogDir,Max,Pattern : identique=True lignes=6 MD5 7C4568BD46033477107E983F6B32E7FC / 7C4568BD46033477107E983F6B32E7FC
Latest,LogDir,Tail : identique=True lignes=7 MD5 2212B5C5D580C0DEFE0ABFE868B07470 / 2212B5C5D580C0DEFE0ABFE868B07470

8.3 Mode un journal : ancienne sortie = préfixe de la nouvelle (état final)

COMMUN-10 : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=34 nouveau=44 ; MD5 ancien=51F74ED5371C255296C739DF1E7E1E41 MD5 prefixe nouveau=51F74ED5371C255296C739DF1E7E1E41
COMMUN-13 : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=31 nouveau=41 ; MD5 ancien=A41B5DF2EF7044E92B292E56BA1C429C MD5 prefixe nouveau=A41B5DF2EF7044E92B292E56BA1C429C
COMMUN-2  : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=39 nouveau=49 ; MD5 ancien=71CCF3712B11995BE83837B33B2E7771 MD5 prefixe nouveau=71CCF3712B11995BE83837B33B2E7771
Journal de compilation U7_Build_20261009-103224.log (aucune famille) : True ; lignes ancien=23 nouveau=32 ; MD5 186E185137E1A09C4EECDFA283BA8271 (égaux)

(Les étiquettes COMMUN-… sont ajoutées ici ; la sortie du script porte « ancien = prefixe du nouveau … » sans étiquette.)

Familles anciennes lues dans la sortie ancienne, comparées au Python :

COMMUN-10 : Lignes 13402 / Personnages 19 / NoSkeletalMesh 2717   | python : lignes=13402, "No SkeletalMesh"=2717, index SPAWN=19
COMMUN-13 : Lignes 17963 / Personnages 11 / NoSkeletalMesh 1473   | python : lignes=17963, "No SkeletalMesh"=1473, index SPAWN=11
COMMUN-2  : Lignes 85697 / Personnages 345 / NoSkeletalMesh 44635 | python : lignes=85697, "No SkeletalMesh"=44635, index SPAWN=345

8.4 Nouvelles familles contre logstats.py

COMMUN-10, sortie PS (section ajoutée) puis sortie brute du Python :

Trame, F5, DESTROY, hitch (motifs de logstats.py) :
  horodatage premier=2026.10.14-10.29.27:693 dernier=2026.10.14-10.42.43:877 ; blocs_trame=2266
  F5 LogSkinnedMeshComp=2712
  F5 par socket : {'CenterBrow': 223, 'Head': 932, 'pelvis': 519, 'chestUpper': 519, 'hip': 519}
  groupes de 5 (Head x2, pelvis, chestUpper, hip) par bloc et acteur = 422
  DESTROY=15
  DESTROY avec groupe F5 dans la trame du DESTROY (meme bloc, meme acteur) = 0 / 15 ; ex. lignes []
  DESTROY sans groupe dans leur trame = 15 ; ex. lignes [2186, 2207, 2732]
  DESTROY avec groupe dans la trame suivante (bloc+1) = 13 / 15
  LPDebug hitch : n=90 max=3888.4 ms mediane=79.7 ms total=16.4 s
--- python
== T : D:\Game\LifePlay_Backup_5.5\COMMUN-10\essais\commun10_LifePlay.log
lignes=13402 premier=2026.10.14-10.29.27:693 dernier=2026.10.14-10.42.43:877 blocs_trame=2266
"No SkeletalMesh" (toutes lignes)=2717 ; F5 LogSkinnedMeshComp=2712
F5 par socket : {'CenterBrow': 223, 'Head': 932, 'pelvis': 519, 'chestUpper': 519, 'hip': 519}
groupes de 5 (Head x2, pelvis, chestUpper, hip) par bloc et acteur = 422
DESTROY=15 ; index SPAWN=19
DESTROY avec groupe F5 dans la trame du DESTROY (meme bloc, meme acteur) = 0 / 15 ; ex. lignes []
DESTROY sans groupe dans leur trame = 15 ; ex. lignes [2186, 2207, 2732]
DESTROY avec groupe dans la trame suivante (bloc+1) = 13 / 15
Accessed None : 10 lignes, 9 couples (fonction, propriete)
LPDebug hitch : n=90 max=3888.4 ms mediane=79.7 ms total=16.4 s

COMMUN-13 (PS puis Python, mêmes libellés) :

  horodatage premier=2026.10.14-13.54.52:970 dernier=2026.10.14-14.21.46:657 ; blocs_trame=2573
  F5 LogSkinnedMeshComp=1473
  F5 par socket : {'CenterBrow': 247, 'Head': 377, 'pelvis': 283, 'chestUpper': 283, 'hip': 283}
  groupes de 5 (Head x2, pelvis, chestUpper, hip) par bloc et acteur = 104
  DESTROY=11
  DESTROY avec groupe F5 dans la trame du DESTROY (meme bloc, meme acteur) = 0 / 11 ; ex. lignes []
  DESTROY sans groupe dans leur trame = 11 ; ex. lignes [2756, 2776, 3823]
  DESTROY avec groupe dans la trame suivante (bloc+1) = 6 / 11
  LPDebug hitch : n=56 max=1350.7 ms mediane=107.5 ms total=10.0 s
--- python (mêmes valeurs : lignes=17963 … blocs_trame=2573 ; F5=1473 ; groupes=104 ; DESTROY=11 ; hitch n=56 max=1350.7 ms mediane=107.5 ms total=10.0 s)

COMMUN-2 (état final, sortie PS ; le Python donne les mêmes 9 lignes) :

  horodatage premier=2026.10.13-16.31.24:563 dernier=2026.10.13-17.13.21:321 ; blocs_trame=19538
  F5 LogSkinnedMeshComp=44630
  F5 par socket : {'CenterBrow': 14652, 'Head': 9689, 'pelvis': 6763, 'chestUpper': 6763, 'hip': 6763}
  groupes de 5 (Head x2, pelvis, chestUpper, hip) par bloc et acteur = 3026
  DESTROY=340
  DESTROY avec groupe F5 dans la trame du DESTROY (meme bloc, meme acteur) = 10 / 340 ; ex. lignes [8414, 10957, 12128]
  DESTROY sans groupe dans leur trame = 330 ; ex. lignes [4083, 4085, 4087]
  DESTROY avec groupe dans la trame suivante (bloc+1) = 80 / 340
  LPDebug hitch : n=108 max=3216.1 ms mediane=71.0 ms total=13.8 s

Verdict de la comparaison ligne à ligne, état final :

D:\Game\LifePlay_Backup_5.5\COMMUN-10\essais\commun10_LifePlay.log : familles nouvelles = logstats.py : True (9 lignes python, 9 lignes PS)
D:\Game\LifePlay_Backup_5.5\COMMUN-13\essais\commun13_LifePlay.log : familles nouvelles = logstats.py : True (9 lignes python, 9 lignes PS)
COMMUN-2 : familles nouvelles = logstats.py : True (9 lignes python, 9 lignes PS)

8.5 -Compare (ancien = préfixe du nouveau) et cas sans famille

=== Compare COMMUN-10 / COMMUN-13 (etat final)
ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=25 nouveau=47 ; MD5 ancien=5A38763669792C4C5516C3768F27F8E5 MD5 prefixe nouveau=5A38763669792C4C5516C3768F27F8E5

Trame, F5, DESTROY, hitch (motifs de logstats.py) :

Mesure                          A      B
------                          -      -
BlocsTrame                   2266   2573
F5LogSkinnedMeshComp         2712   1473
F5Groupes5                    422    104
Destroy                        15     11
DestroyAvecGroupeF5             0      0
DestroySansGroupe              15     11
DestroyGroupeTrameSuivante     13      6
HitchN                         90     56
HitchMaxMs                 3888.4 1350.7
HitchMedianeMs               79.7  107.5
HitchTotalS                  16.4   10.0
F5 socket CenterBrow          223    247
F5 socket Head                932    377
F5 socket pelvis              519    283
F5 socket chestUpper          519    283
F5 socket hip                 519    283

Deux journaux de compilation (U7_Build_20261009-103224.log, U7_Build_20261009-101759.log, aucune ligne de trame) : -Compare ancien = préfixe du nouveau (25 → 39 lignes, MD5 1AF8A6538644AF0FDB94082CE59B639B égaux), table à zéros ; mode un journal : section à zéros (horodatage premier=None dernier=None ; blocs_trame=0, F5 par socket : {}, DESTROY=0, pas de ligne hitch).

-Stats -Pattern DESTROY -Max 1 -Context 0 : la section apparaît après « Acces a None par propriete (15 premieres) » et avant « === Motif 'DESTROY' : 34 lignes sur 13402 ». Le motif libre DESTROY (34 lignes) n'est pas la définition de logstats.py (15 DESTROY rattachés à un index Person_C_n) : deux comptes différents, tous deux justes.

Durées (même journal COMMUN-2, 85 697 lignes) : ancien 0,57 s, nouveau 8,31 s (premier essai : 0,53 s et 8,59 s) ; COMMUN-10 : 0,28 s et 1,10 s.

9. PubCheck

  • Script : pwsh -NoProfile -File "D:\Game\LifePlay-master 5.8\Tools\PubCheck.ps1" -Fichiers "D:\Game\LifePlay-master 5.8\Tools\LogStats.ps1" → PubCheck : 0 constat(s), code 0.
  • Livrable et script ensemble : pwsh -NoProfile -File "D:\Game\LifePlay-master 5.8\Tools\PubCheck.ps1" -Fichiers "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\EVAL2\AMEL1\OUTIL1\OUTIL1-D2-IMPL.md" "D:\Game\LifePlay-master 5.8\Tools\LogStats.ps1" → PubCheck : 0 constat(s), code 0, rejoué après les retouches de E9 et de ce paragraphe, avant la reformulation de cette seule phrase (livrable en LF seul, CR = 0).

10. Ligne d'index proposée pour Tools\LISEZMOI.md (fichier absent, non créé)

| LogStats.ps1 | Compteurs normalisés d'un journal (Accessed None, SPAWN, erreurs), comparaison A/B (-Compare), recherche (-Latest, -Pattern, -Tail) ; depuis OUTIL1-D2 aussi trame, F5 par socket et groupes de 5, DESTROY par trame, LPDebug hitch (ex logstats.py de STE-J6-TRACE) | pwsh -NoProfile -File Tools\LogStats.ps1 <journal> [-Stats] ; pwsh -NoProfile -Command "& 'Tools\LogStats.ps1' -Compare a,b" |

11. Reprise OUTIL1-D2-b : trame calculée seulement avec -Stats (2026-10-14)

Décisions de l'orchestrateur : (P3) trame, F5 par socket, DESTROY par trame et hitch calculés seulement avec -Stats ; sans -Stats, sortie et temps comme avant, -Compare compris ; (P4) « Accessed None par fonction et propriété » non fusionnée, à noter en reste ; (LISEZMOI) créé par la fiche OUTIL1-A, ligne d'index du § 10 transmise.

Modifications (outil Edit, 4 remplacements dans Tools\LogStats.ps1) : aide l. 10-15 (4 lignes au lieu de 3 : « SEULEMENT avec -Stats »), l. 244 _Trame = $(if ($Stats) { Get-LPTrameStats $lines }), l. 252 if ($s._Trame) { Show-LPTrame $s._Trame }, l. 265-271 bloc de fin de -Compare mis dans if ($Stats) { … }. $Stats est lu dans la portée du script comme $Top. Rien d'autre : le corps s'exécute toujours à la lecture.

Instantané avant cette reprise : pwsh -NoProfile -Command "& 'D:\Game\LifePlay-master 5.8\Tools\Snapshot.ps1' -Lot OUTIL1-D2b -Path @('Tools\LogStats.ps1')" →

sauvegarde : Tools\LogStats.ps1 (FB7C7143E70F1CC0242C98EDD8B0FE86)
1 entree(s) ajoutee(s) a D:\Game\LifePlay_Backup_5.5\OUTIL1-D2b\manifest.csv

MD5 et diff (état final), sortie brute :

taille=16027 premiers octets=60,35,10,46
CR=0 LF=289
non-ASCII=12
MD5 apres=A821D3C74FFA7A63A15A5754706A6B1E
MD5 original (OUTIL1-D2)=5CD51FB7F1CFA10CFCC7DD4B2FE3F65D
MD5 etat intermediaire (OUTIL1-D2b)=FB7C7143E70F1CC0242C98EDD8B0FE86
lignes=289
148	0	D:/Game/{LifePlay_Backup_5.5/OUTIL1-D2 => LifePlay-master 5.8}/Tools/LogStats.ps1
12	9	D:/Game/{LifePlay_Backup_5.5/OUTIL1-D2b => LifePlay-master 5.8}/Tools/LogStats.ps1

Dernière ligne : contre l'original, 148 ajoutées, 0 retirée ; contre l'état intermédiaire, 12 ajoutées et 9 retirées (aide, trois lignes de la trame, ré-indentation du bloc -Compare).

Trace : D:\Game\LifePlay-master 5.8\Docs\Migration\etat\EVAL2\AMEL1\OUTIL1\OUTIL1-D2-IMPL.md § 11 — Fiche OUTIL1-D2-b — 2026-10-14
Commande     : même méthode qu'au § 8 : pour chaque journal, @(& $old … | Out-String -Stream) (original, instantané
               OUTIL1-D2) contre @(& $new … | Out-String -Stream) sans puis avec -Stats ; « identique » = même nombre
               de lignes et Compare-Object -CaseSensitive vide ; « préfixe » = $o contre $n[0..($o.Count-1)] ; MD5 de
               chaque texte ; parité = python -I -X utf8 logstats.py T <journal> (Python 3.14.4) contre les lignes
               ajoutées ; durées par Stopwatch autour de chaque appel. Journaux : COMMUN-10, COMMUN-13, COMMUN-2
               (D:\Game\LifePlay_Backup_5.5\COMMUN-*\essais\*.log, lecture seule), journaux de compilation
               U7_Build_20261009-103224.log et -101759.log (Saved\Logs\portage).
Environnement: copie 5.8, branche ue5.8, Tools\LogStats.ps1 MD5 A821D3C7… ; pwsh 7.6.6 ; Python 3.14.4 ; un seul
               processus de mesure à la fois, rien d'autre lancé pendant les durées.
Attendu      : sans -Stats : sortie entière identique à l'original et durée de l'ordre de 0,5 s sur COMMUN-2 ;
               avec -Stats : original = préfixe exact, 9 lignes de parité Python égales.
Obtenu       : ci-dessous.
Lecture      : les durées de COMMUN-2 varient de 0,47 à 0,83 s pour l'ORIGINAL d'un appel à l'autre (cache disque,
               premier appel) : seule l'échelle (0,5 s contre 9,9 s) compte, pas la deuxième décimale.

11.1 Sans -Stats, avec -Stats, -Compare, autres modes (COMMUN-10 et COMMUN-13)

erreurs de syntaxe : 0

=== D:\Game\LifePlay_Backup_5.5\COMMUN-10\essais\commun10_LifePlay.log
sans -Stats : identique (sortie entiere, casse) : True ; lignes ancien=34 nouveau=34 ; MD5 51F74ED5371C255296C739DF1E7E1E41 / 51F74ED5371C255296C739DF1E7E1E41
avec -Stats : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=34 nouveau=44 ; MD5 ancien=51F74ED5371C255296C739DF1E7E1E41 MD5 prefixe nouveau=51F74ED5371C255296C739DF1E7E1E41
avec -Stats : parite logstats.py : True (9 lignes python, 9 lignes PS)

=== D:\Game\LifePlay_Backup_5.5\COMMUN-13\essais\commun13_LifePlay.log
sans -Stats : identique (sortie entiere, casse) : True ; lignes ancien=31 nouveau=31 ; MD5 A41B5DF2EF7044E92B292E56BA1C429C / A41B5DF2EF7044E92B292E56BA1C429C
avec -Stats : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=31 nouveau=41 ; MD5 ancien=A41B5DF2EF7044E92B292E56BA1C429C MD5 prefixe nouveau=A41B5DF2EF7044E92B292E56BA1C429C
avec -Stats : parite logstats.py : True (9 lignes python, 9 lignes PS)

=== -Compare COMMUN-10 / COMMUN-13
sans -Stats : identique (sortie entiere, casse) : True ; lignes ancien=25 nouveau=25 ; MD5 5A38763669792C4C5516C3768F27F8E5 / 5A38763669792C4C5516C3768F27F8E5
avec -Stats : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=25 nouveau=47 ; MD5 ancien=5A38763669792C4C5516C3768F27F8E5 MD5 prefixe nouveau=5A38763669792C4C5516C3768F27F8E5
lignes ajoutees avec -Stats : 22

=== modes -Pattern / -Tail / -Latest : sortie entiere identique
Log,Max,Pattern : identique (sortie entiere, casse) : True ; lignes ancien=15 nouveau=15 ; MD5 606CE80D352AF052231AC22A9E1D1ED9 / 606CE80D352AF052231AC22A9E1D1ED9
Log,Tail : identique (sortie entiere, casse) : True ; lignes ancien=8 nouveau=8 ; MD5 43CCD36CEBA64CF2CA1F75D7D645F12E / 43CCD36CEBA64CF2CA1F75D7D645F12E
Context,Log,Max,Pattern : identique (sortie entiere, casse) : True ; lignes ancien=10 nouveau=10 ; MD5 C4F78E532D3D46426AE909AA22887D8B / C4F78E532D3D46426AE909AA22887D8B
Latest,LogDir,Max,Pattern : identique (sortie entiere, casse) : True ; lignes ancien=6 nouveau=6 ; MD5 7C4568BD46033477107E983F6B32E7FC / 7C4568BD46033477107E983F6B32E7FC
Latest,LogDir,Tail : identique (sortie entiere, casse) : True ; lignes ancien=7 nouveau=7 ; MD5 2212B5C5D580C0DEFE0ABFE868B07470 / 2212B5C5D580C0DEFE0ABFE868B07470

=== -Stats -Pattern DESTROY (section avant le motif)
Acces a None par propriete (15 premieres) :
Trame, F5, DESTROY, hitch (motifs de logstats.py) :
=== Motif 'DESTROY' : 34 lignes sur 13402 (1 premieres)

=== journaux de compilation (aucune famille)
defaut : identique (sortie entiere, casse) : True ; lignes ancien=23 nouveau=23 ; MD5 186E185137E1A09C4EECDFA283BA8271 / 186E185137E1A09C4EECDFA283BA8271
-Stats : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=23 nouveau=32 ; MD5 ancien=186E185137E1A09C4EECDFA283BA8271 MD5 prefixe nouveau=186E185137E1A09C4EECDFA283BA8271
Compare sans -Stats : identique (sortie entiere, casse) : True ; lignes ancien=25 nouveau=25 ; MD5 1AF8A6538644AF0FDB94082CE59B639B / 1AF8A6538644AF0FDB94082CE59B639B
Compare -Stats : ancien = prefixe du nouveau (ligne a ligne, casse) : True ; lignes ancien=25 nouveau=39 ; MD5 ancien=1AF8A6538644AF0FDB94082CE59B639B MD5 prefixe nouveau=1AF8A6538644AF0FDB94082CE59B639B

(La section à zéros du journal de compilation avec -Stats est identique à celle du § 8.5 : horodatage premier=None dernier=None ; blocs_trame=0, F5 par socket : {}, DESTROY=0, sans ligne hitch.)

11.2 COMMUN-2 (85 697 lignes, 15,8 Mo) : durées et parité

=== COMMUN-2 (85697 lignes), duree de chaque appel
ancien                : 0.83 s
nouveau sans -Stats  : 0.59 s
nouveau avec -Stats  : 9.89 s
ancien (2e essai)    : 0.47 s
nouveau sans -Stats (2e essai) : 0.49 s
sans -Stats : identique=True lignes ancien=39 nouveau=39 MD5 71CCF3712B11995BE83837B33B2E7771 / 71CCF3712B11995BE83837B33B2E7771
avec -Stats : prefixe=True lignes ancien=39 nouveau=49 MD5 71CCF3712B11995BE83837B33B2E7771 / 71CCF3712B11995BE83837B33B2E7771
parite logstats.py (avec -Stats) : True (9 lignes python, 9 lignes PS)
Trame, F5, DESTROY, hitch (motifs de logstats.py) :
  horodatage premier=2026.10.13-16.31.24:563 dernier=2026.10.13-17.13.21:321 ; blocs_trame=19538
  F5 LogSkinnedMeshComp=44630
  F5 par socket : {'CenterBrow': 14652, 'Head': 9689, 'pelvis': 6763, 'chestUpper': 6763, 'hip': 6763}
  groupes de 5 (Head x2, pelvis, chestUpper, hip) par bloc et acteur = 3026
  DESTROY=340
  DESTROY avec groupe F5 dans la trame du DESTROY (meme bloc, meme acteur) = 10 / 340 ; ex. lignes [8414, 10957, 12128]
  DESTROY sans groupe dans leur trame = 330 ; ex. lignes [4083, 4085, 4087]
  DESTROY avec groupe dans la trame suivante (bloc+1) = 80 / 340
  LPDebug hitch : n=108 max=3216.1 ms mediane=71.0 ms total=13.8 s

Synthèse des durées sur COMMUN-2 : original 0,83 s puis 0,47 s ; nouveau sans -Stats 0,59 s puis 0,49 s (de l'ordre de l'original) ; nouveau avec -Stats 9,89 s (première version, trame toujours calculée : 8,31 à 8,59 s ; l'écart de 1,3 s entre ces deux mesures de la même boucle n'est pas expliqué, probablement le bruit de la machine).

11.3 PubCheck (état final)

pwsh -NoProfile -File "D:\Game\LifePlay-master 5.8\Tools\PubCheck.ps1" -Fichiers "D:\Game\LifePlay-master 5.8\Tools\LogStats.ps1" → PubCheck : 0 constat(s), code 0 (script). Livrable et script ensemble, après les retouches de la reprise : PubCheck : 0 constat(s), code 0 (livrable en LF seul, CR = 0 ; MD5 du script relu A821D3C74FFA7A63A15A5754706A6B1E).

Reste : « Accessed None par (fonction, propriété) » (E3) ; Tools\LISEZMOI.md (ligne § 10 à transmettre à OUTIL1-A). Tours de la reprise : 10 / 15 (mon décompte, retouche finale et handback compris).

Tours : 30 / 40 (fiche initiale) ; reprise OUTIL1-D2-b : 10 / 15

Constats

  • H est un alias de Get-History dans pwsh : une fonction H est masquée (erreur « paramètre Id »). & script @tableau passe les éléments en arguments positionnels : utiliser une table de hachage pour des paramètres nommés.
  • Sur pwsh 7.6.6, Double.ToString('F1') arrondit comme format(x, '.1f') de Python (égalités exactes comprises) : pas de contournement nécessaire.
  • Les 3 journaux d'essai ont Personnages (compté sans casse) égal à RX_SPAWN du Python ; rien ne garantit l'égalité sur d'autres journaux.