$ErrorActionPreference='Stop' $root='C:/projects/ft/CodexOutput/upserver' $rounds=@('20260928T091527Z','20260928T092018Z','20260928T092245Z','20260928T092418Z','20260928T092948Z') function Compare-Values($before,$after){ $names=[Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) foreach($p in $before.PSObject.Properties){[void]$names.Add($p.Name)} foreach($p in $after.PSObject.Properties){[void]$names.Add($p.Name)} $differences=@(foreach($key in $names){ $a=$before.PSObject.Properties[$key];$b=$after.PSObject.Properties[$key] if($null -eq $a -or $null -eq $b -or ![string]::Equals([string]$a.Value,[string]$b.Value,[StringComparison]::Ordinal)){$key} }) [ordered]@{beforeKeys=@($before.PSObject.Properties).Count;afterKeys=@($after.PSObject.Properties).Count;differentKeys=$differences;ordinalEqual=$differences.Count -eq 0} } $comparison=@() foreach($account in @('101','102')){ $data=@($rounds|ForEach-Object {(Get-Content -LiteralPath "$root/KVGameplayLoopTests/backups/$_/$account.json" -Raw|ConvertFrom-Json).data}) for($i=1;$i -lt $rounds.Count;$i++){$comparison+=[ordered]@{account=$account;before=$rounds[$i-1];after=$rounds[$i];comparison=(Compare-Values $data[$i-1] $data[$i])}} } $runFacts=@() foreach($prefix in @('55088','130200')){ $run=Get-ChildItem "$root/KVGameplayLoopTests/runs" -Directory -Filter ($prefix+'_*') $events=@(Get-Content ($run.FullName+'/events.jsonl')|ForEach-Object {$_|ConvertFrom-Json}) $state=Get-Content (Get-ChildItem ($run.FullName+'/cache') -Recurse -Filter state.json).FullName -Raw|ConvertFrom-Json $final=(Get-Content "$root/KVGameplayLoopTests/backups/20260928T092948Z/101.json" -Raw|ConvertFrom-Json).data $runFacts+=[ordered]@{run=$run.Name;pending=@($state.pending).Count;activeBatch=$state.activeBatch;unknownBatch=$state.unknownBatch;cacheVsFinal=(Compare-Values $state.baseline $final);counts=@($events|Group-Object kind|ForEach-Object {[ordered]@{kind=$_.Name;count=$_.Count}});closed=($events|Where-Object kind -eq 'closed').at} } $result=[ordered]@{createdUtc=[DateTime]::UtcNow.ToString('o');comparisons=$comparison;runs=$runFacts;activePermit=Test-Path "$root/KVGameplayLoopTests/permit.json";claims=@(Get-ChildItem "$root/KVGameplayLoopTests/run-control" -Filter '*.claimed'|ForEach-Object Name);scope='Ordinal comparison of local captured data; event counts are application records, not HTTP request totals'} $result|ConvertTo-Json -Depth 12|Set-Content "$root/Gameplay101Validation/guide-final-reconciliation.json" -Encoding utf8 $result.comparisons|ConvertTo-Json -Depth 7 $result.runs|ConvertTo-Json -Depth 7