param(
    [Parameter(Mandatory=$true)][string]$LegacyRoot,
    [Parameter(Mandatory=$true)][string]$ShortRoot,
    [Parameter(Mandatory=$true)][string]$Report
)
# Reuse genuine old samples, never rerender or relabel them as a new reference.
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
if(-not[IO.Path]::IsPathRooted($Report) -or (Test-Path -LiteralPath $Report)){throw 'Use a new absolute prefix-verification report'}
function Read([string]$Path){Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json}
function Assert([bool]$Condition,[string]$Message){if(-not$Condition){throw $Message}}
function Canonical($Value){
    if($null -eq $Value){return 'null'}
    if($Value -is [bool]){return "bool:$Value"}
    if($Value -is [string]){return ('text:'+($Value | ConvertTo-Json -Compress))}
    if($Value -is [System.ValueType]){return ('number:'+([BitConverter]::ToString([BitConverter]::GetBytes([double]$Value))))}
    if($Value -is [System.Collections.IEnumerable]){return ('['+(@($Value | ForEach-Object {Canonical $_}) -join ',')+']')}
    return ('{'+(@($Value.PSObject.Properties | Sort-Object Name | ForEach-Object {$_.Name+':'+(Canonical $_.Value)}) -join ',')+'}')
}
function Read-OnlyClone($Value){($Value | ConvertTo-Json -Depth 30 -Compress) | ConvertFrom-Json}
$old=Read (Join-Path $LegacyRoot 'manifest.json');$current=Read (Join-Path $ShortRoot 'results.json')
Assert ($old.mode -eq 'audio' -and $old.case_count -eq 7920 -and
    $current.completed_requested_run -and $current.mode -eq 'audio' -and $current.suite -eq 'representative' -and
    $current.measurement_protocol -ceq 'DeepPluck-CPU-Representative-20260903-Rev1' -and
    $current.case_count -eq 39 -and $current.configuration -ceq $old.configuration) 'Invalid prefix provenance/coverage'
if(-not('DeepPluckPrefixProof' -as [type])) {Add-Type -TypeDefinition @'
using System;
using System.IO;
using System.Security.Cryptography;
public static class DeepPluckPrefixProof {
    public static string Check(string oldPath,string newPath,long requiredBytes) {
        var bytes=File.ReadAllBytes(newPath);
        if(bytes.LongLength!=requiredBytes || requiredBytes<=0 || requiredBytes%8!=0)
            throw new InvalidOperationException("Invalid new prefix length");
        using(var stream=File.OpenRead(oldPath)) {
            if(stream.Length<requiredBytes) throw new InvalidOperationException("Old raw file too short");
            var oldBytes=new byte[bytes.Length];int offset=0;
            while(offset<oldBytes.Length) {int n=stream.Read(oldBytes,offset,oldBytes.Length-offset);if(n==0) throw new EndOfStreamException();offset+=n;}
            for(int i=0;i<bytes.Length;i++) if(bytes[i]!=oldBytes[i])
                throw new InvalidOperationException("Old-reference prefix differs at byte "+i);
        }
        for(int i=0;i<bytes.Length;i+=4) {float x=BitConverter.ToSingle(bytes,i);if(float.IsNaN(x)||float.IsInfinity(x)) throw new InvalidOperationException("Nonfinite prefix");}
        using(var hash=SHA256.Create()) return BitConverter.ToString(hash.ComputeHash(bytes)).Replace("-","").ToLowerInvariant();
    }
}
'@}
$legacyCases=@{};foreach($case in $old.cases){$legacyCases[$case.id]=$case}
$rows=[Collections.Generic.List[object]]::new()
foreach($case in $current.cases) {
    $origin=$legacyCases[$case.origin_id]
    Assert ($null -ne $origin -and $case.id -ceq ($origin.id+'-short2') -and
        $case.measurement_window_seconds -eq 2 -and $case.warmup_window_seconds -eq .5 -and
        $case.frames -ge 2*$case.sample_rate -and $case.frames -lt 2*$case.sample_rate+$case.nominal_block_size) 'Invalid derived prefix fixture'
    foreach($field in @('sample_rate','nominal_block_size','polyphony_requested','sustain_load','lifecycle_plan')) {
        Assert ((Canonical $case.$field) -ceq (Canonical $origin.$field)) "Changed prefix input: $($case.id)/$field"
    }
    # APVTS/requested arrays are float32. Compare their exact float32 bits rather
    # than PS5 Decimal versus Double JSON formatting. This introduces no tolerance.
    foreach($field in @('raw_requested','raw_applied','normalized_applied')){
        Assert ($case.$field.Count -eq 62 -and $origin.$field.Count -eq 62) 'Incomplete parameter arrays'
        for($i=0;$i -lt 62;$i++){
            Assert (([BitConverter]::ToString([BitConverter]::GetBytes([single]$case.$field[$i]))) -ceq
                ([BitConverter]::ToString([BitConverter]::GetBytes([single]$origin.$field[$i])))) "Changed float32 input: $($case.id)/$field/$i"
        }
    }
    $expectedEvents=@($origin.events | Where-Object {$_.sample -lt $case.frames})
    Assert ((Canonical @($case.events)) -ceq (Canonical $expectedEvents)) 'Prefix events changed/retimed'
    $oldRow=Read (Join-Path $LegacyRoot ($origin.id+'-rep0.json'))
    $newRow=Read (Join-Path $ShortRoot ($case.id+'-rep0.json'))
    $oldFile=Join-Path $LegacyRoot ($origin.id+'-rep0.f32');$newFile=Join-Path $ShortRoot ($case.id+'-rep0.f32')
    Assert ((Get-Item -LiteralPath $oldFile).Length -eq 8L*$origin.frames -and
        (Get-FileHash -LiteralPath $oldFile).Hash -ieq $oldRow.audio_sha256) 'Original full raw reference changed'
    $hash=[DeepPluckPrefixProof]::Check($oldFile,$newFile,(8L*$case.frames))
    Assert ($hash -ceq $newRow.audio_sha256) 'New prefix raw hash mismatch'
    $limit=[int]($case.frames/$case.nominal_block_size)
    $expectedRuns=@($oldRow.lifecycle_runs | Where-Object {$_.first_block -lt $limit} | ForEach-Object {
        $entry=Read-OnlyClone $_
        $entry.blocks=[int][math]::Min([int]$entry.blocks,$limit-[int]$entry.first_block)
        $entry
    })
    Assert ((Canonical @($newRow.lifecycle_runs)) -ceq (Canonical $expectedRuns)) 'Prefix voice/FX lifecycle differs'
    $rows.Add([pscustomobject]@{id=$case.id;originId=$origin.id;frames=$case.frames;bytes=8L*$case.frames;
        prefixSha256=$hash;legacyWholeAudioSha256=$oldRow.audio_sha256;allSamplesFiniteAndBitIdentical=$true;inputsAndLifecycleEqual=$true})
}
Assert ($rows.Count -eq 39) 'Missing prefix coverage'
[ordered]@{schema='DeepPluck-CPU-Reference-Prefix-Rev1';configuration=$current.configuration;cases=$rows.Count;
    legacyRoot=$LegacyRoot;shortRoot=$ShortRoot;legacyExecutableSha256=$old.executable_sha256;
    shortExecutableSha256=$current.executable_sha256;rows=$rows;passed=$true;
    phaseHashNote='The old full phase hash cannot prove a truncated phase hash; new fresh-double and S/C checks retain exact short phase trajectories.';
    completedUtc=[DateTime]::UtcNow.ToString('o');fullMilestonePass=$false} |
    ConvertTo-Json -Depth 15 | Set-Content -LiteralPath $Report -Encoding utf8
Write-Host "PASS: 39 $($current.configuration) short signals match genuine unchanged legacy sample prefixes and lifecycle"
