param(
    [Parameter(Mandatory=$true)][string]$Executable,
    [Parameter(Mandatory=$true)][string]$OutputRoot
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
$root=Join-Path $OutputRoot ('synthetic-a-'+[guid]::NewGuid().ToString('N').Substring(0,8))
New-Item -ItemType Directory -Path $root | Out-Null
$knownHashFile=Join-Path $root 'sha256-known-input.txt'
[IO.File]::WriteAllBytes($knownHashFile,[Text.Encoding]::ASCII.GetBytes('abc'))
if((Get-FileHash -LiteralPath $knownHashFile -Algorithm SHA256).Hash -ine 'BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD'){
    throw 'Native-host SHA-256 standard-module check failed'
}
# Synthetic unit fixtures are NEVER benchmark/performance evidence. These exercise
# the evidence readers without running or timing any production audio callback.
function Save($Value,[string]$Path){$Value | ConvertTo-Json -Depth 30 | Set-Content -LiteralPath $Path -Encoding utf8}
function Copy-Value($Value){$Value | ConvertTo-Json -Depth 30 | ConvertFrom-Json}
function Assert([bool]$Condition,[string]$Message){if(-not$Condition){throw $Message}}
function Must-Fail([scriptblock]$Action,[string]$Pattern){
    $failed=$false
    try{& $Action}catch{if($_.Exception.Message -notlike $Pattern){throw};$failed=$true}
    Assert $failed "Negative fixture incorrectly passed: $Pattern"
}
function State([int]$Active,[int]$Releasing,[int]$Residual,[bool]$Idle){
    [ordered]@{active_voices=$Active;releasing_voices=$Releasing;residual_voices=$Residual;fx_idle=$Idle}
}
$environmentA=@{environment=@{cpuSets=@(@{cpuSetId=256;logicalIndex=0;coreIndex=0;efficiencyClass=1;flags=0});language='C++20';powerScheme='synthetic unchanged plan'}}
$environmentB=Copy-Value $environmentA;$environmentB.environment.cpuSets[0].flags=1
$stableHash=Get-CPUComparisonEnvironmentHash $environmentA
Assert ($stableHash -ceq (Get-CPUComparisonEnvironmentHash $environmentB)) 'Parked snapshot should not change stable environment identity'
foreach($flag in @(2,4,8)){
    $environmentB.environment.cpuSets[0].flags=$flag
    Assert ($stableHash -cne (Get-CPUComparisonEnvironmentHash $environmentB)) 'Allocated/target/realtime flag incorrectly ignored'
}
$environmentB=Copy-Value $environmentA;$environmentB.environment.powerScheme='changed plan'
Assert ($stableHash -cne (Get-CPUComparisonEnvironmentHash $environmentB)) 'Power plan change incorrectly ignored'
Assert ($environmentA.environment.cpuSets[0].flags -eq 0) 'Comparison helper modified original metadata'
$states=@((State 1 0 0 $false),(State 1 1 0 $false),(State 1 0 1 $false),
    (State 0 0 0 $false),(State 0 0 0 $true),(State 1 0 0 $false),(State 0 0 0 $true))
$plan=@();$runs=@()
for($i=0;$i -lt 7;$i++){
    $plan+=@{samples=$(if($i -eq 6){0}else{64});sample_rate=64000;midi=@($(if($i -eq 5){@{offset=8;midi_hex='903c60'}}))}
    $runs+=@{first_block=$i;blocks=1;before=$states[$i];after=$states[$i]}
}
$case=@{id='T9-synthetic-only';lifecycle_plan=$true;lifecycle_block_plan=$plan;frames=384;events=@()}
$diagnostic=@{manifest_sha256=('a'*64);lifecycle_runs=$runs}
$timing=@{syntheticUnitFixture=$true;completed_requested_run=$true;mode='timing';configuration='Release';
    manifest_version='DeepPluck-CPU-0.11.5-Rev1';selected_manifest_sha256=('b'*64);case_count=1;repeat_count=2;cases=@($case);results=@()}
$audio=@{syntheticUnitFixture=$true;mode='audio';configuration='Release';manifest_version=$timing.manifest_version;
    selected_manifest_sha256=$timing.selected_manifest_sha256;cases=@($case)}
$timingRoot=Join-Path $root 'timing';$audioRoot=Join-Path $root 'audio'
New-Item -ItemType Directory -Path $timingRoot,$audioRoot | Out-Null
Save $diagnostic (Join-Path $audioRoot 'T9-synthetic-only-rep0.json')
Save $audio (Join-Path $audioRoot 'manifest.json')
for($r=0;$r -lt 2;$r++){
    $durations=[double[]]@(1e-5,2e-5,3e-5,4e-5,5e-5,6e-5,1000)
    for($i=0;$i -lt 6;$i++){$durations[$i]*=$r+1}
    $bytes=New-Object byte[] ($durations.Length*8)
    [Buffer]::BlockCopy($durations,0,$bytes,0,$bytes.Length)
    [IO.File]::WriteAllBytes((Join-Path $timingRoot "T9-synthetic-only-rep$r.timings.f64"),$bytes)
    $sum=0.0;$audioSeconds=0.0
    for($i=0;$i -lt 6;$i++){$sum+=$durations[$i];$audioSeconds+=.001}
    $timing.results+=@{id=$case.id;repeat=$r;manifest_sha256=$diagnostic.manifest_sha256;
        total=@{rtf=$sum/$audioSeconds;callback_seconds=$sum;audio_seconds=$audioSeconds;
            max_block_seconds=$durations[5];max_block_ratio=$durations[5]/.001;deadline_misses=0}}
}
Save $timing (Join-Path $timingRoot 'results.json')
$positive=Join-Path $root 'positive'
& (Join-Path $PSScriptRoot 'AnalyzeCPUBenchmark.ps1') -TimingRoot $timingRoot -AudioRoot $audioRoot -Output $positive
$phases=@(Import-Csv -LiteralPath (Join-Path $positive 'diagnostic-phases.csv'))
Assert ($phases.Count -eq 12) 'Expected six diagnostic categories per repetition'
foreach($phase in $phases){Assert ([int]$phase.blocks -eq 1 -and [int]$phase.frames -eq 64) 'Wrong synthetic phase membership'}
foreach($phase in $phases){Assert ($phase.callbackSeconds -match '^[0-9.eE+\-]+$') 'CSV numbers must use invariant decimal points'}
$analysis=Get-Content -LiteralPath (Join-Path $positive 'analysis.json') -Raw | ConvertFrom-Json
Assert ($analysis.dynamicPhaseViolations.Count -eq 0 -and -not$analysis.fullMilestonePass) 'Synthetic test must never approve a milestone'
# Exceed B's advisory target while all deadlines still pass. Soft targets must
# remain visible but must not be reinterpreted as hard acceptance failures.
$softTiming=Copy-Value $timing;$softAudio=Copy-Value $audio
$softTiming.cases[0].id='B-synthetic-only';$softAudio.cases[0].id='B-synthetic-only'
$softTimingRoot=Join-Path $root 'soft-timing';$softAudioRoot=Join-Path $root 'soft-audio'
New-Item -ItemType Directory -Path $softTimingRoot,$softAudioRoot | Out-Null
for($r=0;$r -lt 2;$r++){
    $row=$softTiming.results[$r];$row.id='B-synthetic-only'
    $row.total | Add-Member -NotePropertyName valid -NotePropertyValue $true
    $bytes=[IO.File]::ReadAllBytes((Join-Path $timingRoot "T9-synthetic-only-rep$r.timings.f64"))
    $values=New-Object double[] ($bytes.Length/8);[Buffer]::BlockCopy($bytes,0,$values,0,$bytes.Length)
    for($i=0;$i -lt 6;$i++){$values[$i]*=5}
    $sum=0.0;for($i=0;$i -lt 6;$i++){$sum+=$values[$i]}
    $row.total.callback_seconds=$sum;$row.total.rtf=$sum/$row.total.audio_seconds
    $row.total.max_block_seconds=$values[5];$row.total.max_block_ratio=$values[5]/.001
    [Buffer]::BlockCopy($values,0,$bytes,0,$bytes.Length)
    [IO.File]::WriteAllBytes((Join-Path $softTimingRoot "B-synthetic-only-rep$r.timings.f64"),$bytes)
}
Save $softTiming (Join-Path $softTimingRoot 'results.json');Save $softAudio (Join-Path $softAudioRoot 'manifest.json')
Save $diagnostic (Join-Path $softAudioRoot 'B-synthetic-only-rep0.json')
$softOutput=Join-Path $root 'soft-targets'
& (Join-Path $PSScriptRoot 'AnalyzeCPUBenchmark.ps1') -TimingRoot $softTimingRoot -AudioRoot $softAudioRoot -Output $softOutput
$soft=Get-Content -LiteralPath (Join-Path $softOutput 'analysis.json') -Raw | ConvertFrom-Json
Assert ($soft.dynamicPhaseViolations.Count -eq 0 -and $soft.softTargets.violations.Count -gt 0) 'Soft target incorrectly became a hard failure or disappeared'
Assert (@($soft.softTargets.violations | Where-Object {$_.kind -eq 'static'}).Count -eq 1 -and
    @($soft.softTargets.violations | Where-Object {$_.kind -eq 'diagnostic'}).Count -gt 0) 'Missing static or diagnostic advisory reporting'
foreach($name in @('duplicate-repeat','missing-block','summary-mismatch','input-mismatch')){
    $changed=Copy-Value $timing;$changedDiagnostic=Copy-Value $diagnostic
    $pattern=''
    switch($name){
        'duplicate-repeat' {$changed.results[1].repeat=0;$pattern='Duplicate repetition*'}
        'missing-block' {$changedDiagnostic.lifecycle_runs[6].blocks=2;$pattern='Incomplete lifecycle run coverage'}
        'summary-mismatch' {$changed.results[0].total.callback_seconds=123;$pattern='Block data/summary mismatch*'}
        'input-mismatch' {$changed.results[1].manifest_sha256='c'*64;$pattern='Duplicate repetition*'}
    }
    $negativeTiming=Join-Path $root "$name-input";New-Item -ItemType Directory -Path $negativeTiming | Out-Null
    Save $changed (Join-Path $negativeTiming 'results.json')
    foreach($r in @(0,1)){Copy-Item -LiteralPath (Join-Path $timingRoot "T9-synthetic-only-rep$r.timings.f64") -Destination $negativeTiming}
    $negativeAudio=Join-Path $root "$name-audio";New-Item -ItemType Directory -Path $negativeAudio | Out-Null
    Save $changedDiagnostic (Join-Path $negativeAudio 'T9-synthetic-only-rep0.json')
    Save $audio (Join-Path $negativeAudio 'manifest.json')
    Must-Fail {& (Join-Path $PSScriptRoot 'AnalyzeCPUBenchmark.ps1') -TimingRoot $negativeTiming -AudioRoot $negativeAudio -Output (Join-Path $root $name)} $pattern
}
# A standalone full-baseline checker must reject incomplete quick/fixture data.
Must-Fail {& (Join-Path $PSScriptRoot 'VerifyCPUBaselineAudio.ps1') -Root $timingRoot -Report (Join-Path $root 'invalid-full-pass.json')} 'Incomplete full fresh-double baseline'
foreach($variant in @('gain','regression','unchanged')){
    $candidateValue=switch($variant){'gain'{.9};'regression'{1.04};'unchanged'{1.0}}
    $inputFile=Join-Path $root "$variant-pairs.json"
    Save @{syntheticUnitFixture=$true;pairs=@(0..8 | ForEach-Object {@{baseline_rtf=1.0;candidate_rtf=$candidateValue}})} $inputFile
    $json= & $Executable --pair-statistics $inputFile
    Assert ($LASTEXITCODE -eq 0) 'Pair statistics failed'
    $statistics=($json | Out-String) | ConvertFrom-Json
    Assert ($statistics.efficiency_threshold_pass -eq ($variant -eq 'gain')) 'Incorrect gain threshold'
    Assert ($statistics.confirmed_regression -eq ($variant -eq 'regression')) 'Incorrect regression threshold'
    Assert (-not$statistics.full_milestone_pass) 'Statistics alone must never approve a milestone'
    Save $statistics (Join-Path $root "$variant-statistics.json")
}
# Exercise chronological pair assembly, original per-pair retention and its
# fixed-seed report using explicitly synthetic callback data, not actual timing.
$pairedRoot=Join-Path $root 'synthetic-pairs';New-Item -ItemType Directory -Path $pairedRoot | Out-Null
$executableHash=(Get-FileHash -LiteralPath $Executable).Hash
$epoch=[DateTimeOffset]'2026-01-01T00:00:00Z';$sequence=0;$pairedSchedule=@()
for($r=0;$r -lt 9;$r++){
    $order=if($r%2 -eq 0){@('S','C')}else{@('C','S')}
    foreach($side in $order){
        $directory=Join-Path $pairedRoot "B-synthetic-only-pair$r-$side";New-Item -ItemType Directory -Path $directory | Out-Null
        $time=if($side -eq 'S'){.0001}else{.00009}
        $metric=@{valid=$true;blocks=1;rtf=$time/.001;callback_seconds=$time;audio_seconds=.001;
            max_block_seconds=$time;max_block_ratio=$time/.001;deadline_misses=0;p99_seconds=$time;p999_seconds=$time}
        $fixture=@{syntheticUnitFixture=$true;completed_requested_run=$true;configuration='Release';mode='timing';case_count=1;
            cases=@(@{id='B-synthetic-only'});results=@(@{id='B-synthetic-only';repeat=0;manifest_sha256=('a'*64);total=$metric;phases=@()});
            repeat_count=1;executable_sha256=$executableHash;target_version='synthetic-not-a-product-version';
            selected_manifest_sha256=('b'*64);manifest_version='DeepPluck-CPU-0.11.5-Rev1';
            started_utc=$epoch.AddSeconds($sequence).ToString('o');completed_utc=$epoch.AddSeconds($sequence+.1).ToString('o');
            metadata_sha256=('c'*64);provenance=@{source_sha256=('d'*64);environment_sha256=('e'*64);environment=@{profiling=$false;debugger=$false}}}
        Save $fixture (Join-Path $directory 'results.json')
        [IO.File]::WriteAllBytes((Join-Path $directory 'B-synthetic-only-rep0.timings.f64'),[BitConverter]::GetBytes([double]$time))
        $sequence++
    }
    $pairedSchedule+=@{id='B-synthetic-only';pair=$r;order=$order}
}
Save @{syntheticUnitFixture=$true;complete=$true;pairs=9;schedule=$pairedSchedule} (Join-Path $pairedRoot 'paired.json')
& (Join-Path $PSScriptRoot 'AnalyzeCPUPairs.ps1') -Root $pairedRoot -BaselineExecutable $Executable -CandidateExecutable $Executable -Output (Join-Path $root 'synthetic-pairs-positive')
$paired=Get-Content -LiteralPath (Join-Path $root 'synthetic-pairs-positive/paired-analysis.json') -Raw | ConvertFrom-Json
Assert ($paired.syntheticUnitFixture -and $paired.caseCount -eq 1 -and $paired.pairCount -eq 9 -and $paired.candidateHardGatesPass -and
    $paired.comparisons[0].statistics.efficiency_threshold_pass -and -not$paired.fullMilestonePass) 'Incorrect synthetic pair assembly'
$invalidPairs=Join-Path $root 'synthetic-pairs-invalid';New-Item -ItemType Directory -Path $invalidPairs | Out-Null
Save @{syntheticUnitFixture=$true;complete=$true;pairs=9;schedule=@(@{id='B-synthetic-only';pair=0;order=@('C','S')})} (Join-Path $invalidPairs 'paired.json')
Must-Fail {& (Join-Path $PSScriptRoot 'AnalyzeCPUPairs.ps1') -Root $invalidPairs -BaselineExecutable $Executable -CandidateExecutable $Executable -Output (Join-Path $root 'synthetic-pairs-negative')} 'Order must alternate*'
$dummy=Join-Path $root 'synthetic-not-evidence.json';Save @{syntheticUnitFixture=$true} $dummy
$gate=Join-Path $root 'performance-negative.json'
$syntheticAnalysis=Join-Path $root 'synthetic-pairs-positive/paired-analysis.json'
Must-Fail {& (Join-Path $PSScriptRoot 'VerifyCPUPerformanceAcceptance.ps1') -FullPairedAnalysis $syntheticAnalysis -CandidatePhaseAnalysis (Join-Path $positive 'analysis.json') -CandidateExecutable $Executable -Experiment $dummy -FirstConfirmation $syntheticAnalysis -SecondConfirmation $syntheticAnalysis -LatencyReview $dummy -Report $gate} 'Full genuine five-pair candidate hard gates are incomplete'
$gateResult=Get-Content -LiteralPath $gate -Raw | ConvertFrom-Json
Assert (-not$gateResult.performancePass -and $gateResult.error) 'Synthetic/incomplete evidence incorrectly approved performance'
Save @{syntheticUnitTests=$true;passed=$true;fullMilestonePass=$false} (Join-Path $root 'unit-test-result.json')
Write-Host 'PASS: synthetic analysis and statistics unit tests (not CPU measurements)'
