param(
    [Parameter(Mandatory=$true)][string]$EvidenceRoot,
    [Parameter(Mandatory=$true)][string]$CandidateRoot,
    [Parameter(Mandatory=$true)][string]$ScreeningSummary,
    [Parameter(Mandatory=$true)][string]$BaselineRoot,
    [Parameter(Mandatory=$true)][string]$StartingSnapshot,
    [Parameter(Mandatory=$true)][string]$BaselineIdentity,
    [string]$BuildDir='D:/0A_VST_Development/build'
)
# Normal-priority serial measurements only. Never run alongside builds, CTest,
# another render/profile, or a previous performance invocation. No system tweaks.
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$root=[IO.Path]::GetFullPath($EvidenceRoot)
if(-not $root.StartsWith((Join-Path $repo 'Artifacts\V0.12.0')+'\',[StringComparison]::OrdinalIgnoreCase)){throw 'Evidence must be project-local'}
if(Test-Path -LiteralPath $root){throw 'Preserve previous measurements; new series requires new evidence root'}
New-Item -ItemType Directory -Path $root | Out-Null
function Json($Value,[string]$Path){
    if(Test-Path -LiteralPath $Path){throw 'Do not overwrite evidence'}
    [IO.File]::WriteAllText($Path,($Value|ConvertTo-Json -Depth 35),[Text.UTF8Encoding]::new($false))
}
function Read-Json([string]$Path){Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json}
function Hash-Text([string]$Text){
    $hash=[Security.Cryptography.SHA256]::Create()
    try{([BitConverter]::ToString($hash.ComputeHash([Text.Encoding]::UTF8.GetBytes($Text)))).Replace('-','').ToLowerInvariant()}finally{$hash.Dispose()}
}
function Median($Values){$sorted=@($Values|Sort-Object);if($sorted.Count -eq 0){throw 'Missing measurements'};return [double]$sorted[[int][Math]::Floor($sorted.Count/2)]}
$screen=Read-Json $ScreeningSummary
$manifest=Read-Json (Join-Path $CandidateRoot 'candidate-manifest.json')
if($screen.mode -ne 'candidates' -or $screen.count -ne 160 -or $manifest.entries.Count -ne 160){throw 'Complete initial screening required'}
$cases=[Collections.Generic.List[object]]::new()
foreach($id in @('B-r48000-b128-wave0-short2','C-r48000-b128-wave0-short2','D-r48000-b128-wave4-order0-short2','D-r96000-b64-wave4-order0-short2')){
    $cases.Add([ordered]@{id=$id;kind='benchmark';preset='';presetSha256=''})
}
foreach($category in @('deep','warm','bright','emotional','atmospheric','arp','poly','experimental')){
    $selected=$null
    foreach($candidate in @($manifest.entries|Where-Object{$_.category -eq $category}|Sort-Object index)){
        $row=@($screen.results|Where-Object{$_.id -ceq $candidate.id -and $_.sha256 -ceq $candidate.sha256 -and $_.technicalPass})
        if($row.Count -eq 1){$selected=$candidate;break}
    }
    if($null -eq $selected){throw "No technically valid representative for $category"}
    $preset=[IO.Path]::GetFullPath((Join-Path $CandidateRoot $selected.path))
    if((Get-FileHash -LiteralPath $preset).Hash -ine $selected.sha256){throw 'Representative changed'}
    $cases.Add([ordered]@{id=$selected.name;candidateId=$selected.id;category=$category;kind='preset';preset=$preset;presetSha256=$selected.sha256})
}
$binaries=@{
    'before-benchmark'=(Join-Path $BaselineRoot 'DeepPluckBenchmark.exe')
    'after-benchmark'=(Join-Path $BuildDir 'DeepPluckBenchmark_artefacts/Release/DeepPluckBenchmark.exe')
    'before-preset'=(Join-Path $BaselineRoot 'DeepPluckPresetAudit.exe')
    'after-preset'=(Join-Path $BuildDir 'DeepPluckPresetAudit_artefacts/Release/DeepPluckPresetAudit.exe')
}
$binaryRecords=@($binaries.GetEnumerator()|Sort-Object Name|ForEach-Object{
    $file=Get-Item -LiteralPath $_.Value
    $expected=if($_.Name.StartsWith('before-')){'0.11.5'}else{'0.12.0'}
    if($file.VersionInfo.FileVersion -notin @($expected,$expected+'.0')){throw 'Unexpected performance binary role/version'}
    [ordered]@{role=$_.Name;path=$file.FullName;sha256=(Get-FileHash -LiteralPath $file.FullName).Hash;version=$file.VersionInfo.FileVersion}
})
$plan=[ordered]@{protocol='DeepPluck-V0.12.0-CPU-Rev1';warmupSeconds=.5;measurementSeconds=2;pairs=5;
    confirmation='At most one further five-pair series, only for initial >3% median or new deadline misses; never discard the first series';
    selection='Lowest index with successful initial technical screening per category, fixed before any timing';cases=@($cases.ToArray());binaries=$binaryRecords;
    screeningSha256=(Get-FileHash -LiteralPath $ScreeningSummary).Hash;manifestSha256=(Get-FileHash -LiteralPath (Join-Path $CandidateRoot 'candidate-manifest.json')).Hash;
    baselineIdentitySha256=(Get-FileHash -LiteralPath $BaselineIdentity).Hash;historicalElevenMisses='Deferred, not retroactively cleared';noPriorityAffinityPowerChanges=$true}
Json $plan (Join-Path $root 'plan.json')
# Capture the present machine once for BOTH sides; only source/build provenance differs.
$capture=Join-Path $root 'environment-capture.json'
& (Join-Path $PSScriptRoot 'CaptureCPUBenchmarkEnvironment.ps1') -OutputFile $capture -Configuration Release -BuildDir $BuildDir -Role Candidate -ProductionOptimized
$current=Read-Json $capture
$paths=@('CMakeLists.txt','AGENTS.md')+@(rg --files Source Tests Tools CMake Presets/Factory Docs/Sound)
$current.source=@($paths|Sort-Object -Unique|ForEach-Object{[ordered]@{path=$_;sha256=(Get-FileHash -LiteralPath (Join-Path $repo $_)).Hash}})
$current.source_sha256=Hash-Text ($current.source|ConvertTo-Json -Depth 8 -Compress)
Json $current (Join-Path $root 'after-metadata.json')
$before=Read-Json $capture
$start=Read-Json $StartingSnapshot
$before.source=@($start.files)
$before.source_sha256=Hash-Text ($before.source|ConvertTo-Json -Depth 8 -Compress)
$before.role='Baseline'
$before | Add-Member -NotePropertyName preservedBaselineIdentity -NotePropertyValue (Read-Json $BaselineIdentity)
$before | Add-Member -NotePropertyName sourceBasis -NotePropertyValue 'Preserved current 0.11.5 working-copy inventory; same current measurement environment, not a reconstruction from HEAD.'
Json $before (Join-Path $root 'before-metadata.json')
function Invoke-One($Case,[string]$Side,[int]$Pair,[int]$Series){
    $binary=$binaries[$Side+'-'+$Case.kind]
    $target=Join-Path $root ($Case.id+'-series'+$Series+'-pair'+$Pair+'-'+$Side)
    if($Case.kind -eq 'benchmark'){
        $arguments=@('--suite','representative','--case',$Case.id,'--mode','timing','--repeat','1','--output',$target,'--metadata',(Join-Path $root ($Side+'-metadata.json')))
    }else{
        $arguments=@('--preset',$Case.preset,'--output',$target,'--mode','timing','--scene','short','--rate','48000','--buffer','128','--voices','8','--fx','wet')
    }
    $started=[DateTime]::UtcNow.ToString('o')
    $nativeText=@(& $binary @arguments)
    $native=$LASTEXITCODE
    $nativeText|ForEach-Object{Write-Host $_}
    if(-not(Test-Path -LiteralPath $target)){New-Item -ItemType Directory -Path $target|Out-Null}
    Json ([ordered]@{nativeExit=$native;binary=$binary;arguments=$arguments;startedUtc=$started;completedUtc=[DateTime]::UtcNow.ToString('o')}) (Join-Path $target 'invocation.json')
    if($native -ne 0){throw "Measurement failed: $target (exit $native)"}
    if($Case.kind -eq 'benchmark'){
        $run=Read-Json (Join-Path $target 'results.json');$result=$run.results[0];$stats=$result.total
        return [ordered]@{rtf=[double]$stats.rtf;medianBlockSeconds=$stats.p50_seconds;p99BlockSeconds=$stats.p99_seconds;
            maxBlockSeconds=$stats.max_block_seconds;deadlineMisses=[int]$stats.deadline_misses;
            manifestHash=$run.selected_manifest_sha256;raw=$run.cases[0].raw_requested;
            rawMeaning='Requested fixed snapshot; the unchanged harness validates the actual canonical APVTS application';
            timer=(Read-Json (Join-Path $target 'timer.json'));path=$target}
    }
    $result=Read-Json (Join-Path $target 'result.json')
    return [ordered]@{rtf=[double]$result.rtf;medianBlockSeconds=$result.medianBlockSeconds;p99BlockSeconds=$result.p99BlockSeconds;
        maxBlockSeconds=$result.maxBlockSeconds;deadlineMisses=[int]$result.deadlineMisses;
        manifestHash=$Case.presetSha256;raw=$result.appliedValues;timerMinimum=$result.timerMinimumSeconds;path=$target}
}
$summary=[Collections.Generic.List[object]]::new()
foreach($case in $cases){
    $seriesRecords=[Collections.Generic.List[object]]::new()
    for($series=0;$series -lt 2;$series++){
        $pairs=[Collections.Generic.List[object]]::new()
        for($pair=0;$pair -lt 5;$pair++){
            $order=if(($pair+$series)%2 -eq 0){@('before','after')}else{@('after','before')}
            $measurements=@{}
            foreach($side in $order){$measurements[$side]=Invoke-One $case $side $pair $series}
            $a=$measurements.before;$b=$measurements.after
            if($a.raw.Count -ne 62 -or $b.raw.Count -ne 62 -or $a.manifestHash -cne $b.manifestHash -or ($a.raw|ConvertTo-Json -Compress) -cne ($b.raw|ConvertTo-Json -Compress)){throw 'Paired inputs differ or are incomplete'}
            if($a.rtf -le 0 -or $b.rtf -le 0){throw 'Invalid timer result'}
            $pairs.Add([ordered]@{pair=$pair;order=$order;before=$a;after=$b;extraCpuPercent=100.0*($b.rtf/$a.rtf-1)})
        }
        $differences=@($pairs|ForEach-Object{$_.extraCpuPercent})
        $median=Median $differences
        $newMisses=($pairs|ForEach-Object{$_.after.deadlineMisses}|Measure-Object -Sum).Sum
        $oldMisses=($pairs|ForEach-Object{$_.before.deadlineMisses}|Measure-Object -Sum).Sum
        $record=[ordered]@{series=$series;medianExtraCpuPercent=$median;minimumExtraCpuPercent=($differences|Measure-Object -Minimum).Minimum;
            maximumExtraCpuPercent=($differences|Measure-Object -Maximum).Maximum;beforeDeadlineMisses=$oldMisses;afterDeadlineMisses=$newMisses;pairs=@($pairs.ToArray())}
        Json $record (Join-Path $root ($case.id+'-series'+$series+'.json'));$seriesRecords.Add($record)
        if($series -eq 0 -and $median -le 3 -and $newMisses -eq 0){break}
    }
    $latest=$seriesRecords[$seriesRecords.Count-1]
    $allNewMisses=($seriesRecords|ForEach-Object{$_.afterDeadlineMisses}|Measure-Object -Sum).Sum
    $status=if($allNewMisses -gt 0){'OPEN-deadline-review'}elseif($latest.medianExtraCpuPercent -gt 3){
        if($latest.minimumExtraCpuPercent -gt 0){'FAIL-confirmed-cpu-regression'}else{'OPEN-inconclusive-scatter'}
    }else{'PASS-no-confirmed-regression'}
    $item=[ordered]@{id=$case.id;status=$status;series=@($seriesRecords.ToArray());historicalTimingGateCleared=$false}
    $summary.Add($item);Write-Host "$($case.id): $status"
}
Json ([ordered]@{protocol=$plan.protocol;caseCount=$summary.Count;results=@($summary.ToArray());
    comparisonPass=(@($summary|Where-Object{$_.status -ne 'PASS-no-confirmed-regression'}).Count -eq 0);
    manualApproval='OPEN';historicalTimingGate='Deferred, not retroactively cleared'}) (Join-Path $root 'summary.json')
