<# .SYNOPSIS Compteurs normalises d'un journal LifePlay, ou comparaison A/B de deux journaux. .DESCRIPTION Reconnait les messages anglais (4.27) et francais (editeur 5.5 localise). Normalise par le nombre de personnages generes (lignes "index SPAWN"), selon le protocole du rapport du 2026-09-28 (paragraphe 8). .EXAMPLE .\Tools\LogStats.ps1 Saved\Logs\LifePlay.log .\Tools\LogStats.ps1 -Compare D:\Game\LifePlay_Build_427_Dev\WindowsNoEditor\LifePlay\Saved\Logs\LifePlay.log, Saved\Logs\LifePlay.log #> param( [Parameter(Position = 0)][string]$Log, [string[]]$Compare, [int]$Top = 15 ) $ErrorActionPreference = 'Stop' $reNone = 'Accessed None trying to read property (\w+)|Acc[eè]s [aà] None, tentative de lecture de la propri[eé]t[eé] (\w+)' function Get-LogStats([string]$Path) { $lines = [IO.File]::ReadAllLines((Resolve-Path $Path)) $spawnLines = $lines -match 'index SPAWN' $spawnActors = $spawnLines | ForEach-Object { if ($_ -match '\] (Person_C_\d+) index SPAWN') { $Matches[1] } } | Sort-Object -Unique $noneProps = foreach ($l in $lines) { if ($l -match $reNone) { if ($Matches[1]) { $Matches[1] } else { $Matches[2] } } } $noneMesh = @($noneProps | Where-Object { $_ -match 'mesh' }).Count $files = @{} foreach ($l in ($lines -match "GetAllFilesFromFolder \| '([^']+)' ext='([^']+)' -> (\d+)")) { if ($l -match "GetAllFilesFromFolder \| '([^']+)' ext='([^']+)' -> (\d+)") { $files["$($Matches[1]) $($Matches[2])"] = [int]$Matches[3] } } $n = [math]::Max(1, $spawnLines.Count) [pscustomobject]@{ Fichier = $Path Lignes = $lines.Count Personnages = $spawnLines.Count ActeursDistincts = @($spawnActors).Count AccessedNone = @($noneProps).Count AccessedNoneMaillage = $noneMesh NoneMaillageParPerso = [math]::Round($noneMesh / $n, 2) NoSkeletalMesh = @($lines -match 'No SkeletalMesh').Count IndexHorsBornes = @($lines -match 'Attempted to access index|Tentative d.acc[eè]s [aà] l.index|from array .* of length').Count LoadTxtOK = @($lines -match 'LoadTxt OK').Count LoadTxtEchec = @($lines -match 'LoadTxt (ECHEC|FAIL)').Count LogClassError = @($lines -match 'LogClass: Error').Count FailedToLoad = @($lines -match "Failed to load '/Game/").Count Fatal = @($lines -match 'Fatal error|LogWindows: Error: ===').Count Erreurs = @($lines -match ': Error: ').Count Avertissements = @($lines -match ': Warning: ').Count LogLifePlay = @($lines -match 'LogLifePlay').Count LogLPAnimMeasure = @($lines -match 'LogLPAnimMeasure').Count TraceModding = @($lines -match 'LogLPModdingTrace|LogModdingExtra').Count _Proprietes = $noneProps | Group-Object | Sort-Object Count -Descending | Select-Object -First $Top Count, Name _Fichiers = $files } } function Show([object]$s) { $s | Select-Object * -ExcludeProperty _* | Format-List "Acces a None par propriete ($Top premieres) :" $s._Proprietes | Format-Table -AutoSize } if ($Compare) { if ($Compare.Count -ne 2) { throw '-Compare attend exactement deux journaux : reference, cible' } $a = Get-LogStats $Compare[0]; $b = Get-LogStats $Compare[1] $props = ($a | Select-Object * -ExcludeProperty _*, Fichier).PSObject.Properties.Name "A = $($Compare[0])`nB = $($Compare[1])`n" $props | ForEach-Object { [pscustomobject]@{ Mesure = $_; A = $a.$_; B = $b.$_ } } | Format-Table -AutoSize "Fichiers enumeres (GetAllFilesFromFolder) qui different :" $keys = @($a._Fichiers.Keys) + @($b._Fichiers.Keys) | Sort-Object -Unique $diff = $keys | Where-Object { $a._Fichiers[$_] -ne $b._Fichiers[$_] } | ForEach-Object { [pscustomobject]@{ Dossier = $_; A = $a._Fichiers[$_]; B = $b._Fichiers[$_] } } if ($diff) { $diff | Format-Table -AutoSize } else { " aucun ($($keys.Count) enumerations identiques)" } } elseif ($Log) { Show (Get-LogStats $Log) } else { throw 'Indiquer un journal, ou -Compare reference,cible' }