<# .SYNOPSIS Campaign V blank-world A/B attribution probe: interleaves two client builds over the same connected route and reports the blank rate per build. .DESCRIPTION tools/run-repeat-connected-gate.ps1 answers "does THIS build blank?" It cannot answer "does this build blank BECAUSE of the change?", because the blank rate drifts with machine state - desktop composition, foreground window, driver residency - between one ten-run block and the next. A block of A followed by a block of B therefore confounds the change with whatever else moved in those twenty minutes, and that is precisely how a build gets blamed for an ambient defect. This script runs A and B strictly alternately in one block, so any drift is shared by both arms, and prints a per-arm verdict table. Identical rates exonerate the change under test; a rate that follows the binary implicates it. Each cycle is the repeat gate's cycle exactly: launch connected -> teleloc to the pinned cell -> wait world-visible -> settle -> screenshot -> graceful close -> cooldown. The verdict is the same screenshot-size test, so the two scripts' numbers are directly comparable. .PARAMETER Pairs A/B pairs to run. Default 5 (ten launches). .PARAMETER ExeA / ExeB The two client executables. Give each a stable staging directory - not the build output, which the next `dotnet build` overwrites. .PARAMETER LabelA / LabelB Names for the arms in the verdict table. .PARAMETER Teleloc The pinned location, as the full /teleloc argument string. Same default as the repeat gate; the failure rate is location-sensitive. .PARAMETER MinRenderedBytes Screenshot size below which a run is called blank. Default 500000. #> [CmdletBinding()] param( [int]$Pairs = 5, [Parameter(Mandatory = $true)][string]$ExeA, [Parameter(Mandatory = $true)][string]$ExeB, [string]$LabelA = 'A', [string]$LabelB = 'B', [string]$Teleloc = "0x2E430012 57.895 42.116 16.802 1 0 0 0", [int]$MinRenderedBytes = 500000 ) $ErrorActionPreference = 'Stop' foreach ($exe in @($ExeA, $ExeB)) { if (-not (Test-Path $exe)) { throw "Client not found at $exe." } } # Never race a session someone is already playing: same account, same server. if (Get-Process -Name AcDream.App -ErrorAction SilentlyContinue) { throw 'AcDream.App is already running. This probe uses the shared test account and must not steal its session.' } $root = Join-Path $env:TEMP "claude\blank-world-ab-$([DateTime]::Now.ToString('HHmmss'))" New-Item -ItemType Directory -Force -Path $root | Out-Null $results = @() for ($pair = 1; $pair -le $Pairs; $pair++) { foreach ($arm in @( @{ Label = $LabelA; Exe = $ExeA }, @{ Label = $LabelB; Exe = $ExeB })) { $dir = Join-Path $root ("pair-{0}-{1}" -f $pair, $arm.Label) New-Item -ItemType Directory -Force -Path $dir | Out-Null Set-Content -Encoding utf8 (Join-Path $dir 'probe.txt') -Value @" wait world-ready 90000 command /teleloc $Teleloc wait materialized 1 90000 wait world-visible 30000 sleep 15000 screenshot ab-run 30000 sleep 1000 "@ $env:ACDREAM_DAT_DIR = Join-Path $env:USERPROFILE "Documents\Asheron's Call" $env:ACDREAM_LIVE = '1' $env:ACDREAM_TEST_HOST = '127.0.0.1' $env:ACDREAM_TEST_PORT = '9000' $env:ACDREAM_TEST_USER = 'testaccount' $env:ACDREAM_TEST_PASS = 'testpassword' $env:ACDREAM_RETAIL_UI = '1' $env:ACDREAM_UI_PROBE_SCRIPT = Join-Path $dir 'probe.txt' $env:ACDREAM_AUTOMATION_ARTIFACT_DIR = $dir $log = Join-Path $dir 'client.log' $proc = Start-Process -FilePath $arm.Exe -RedirectStandardOutput $log ` -RedirectStandardError "$log.err" -PassThru -WindowStyle Minimized $shot = Join-Path $dir 'screenshots\ab-run.png' $deadline = (Get-Date).AddSeconds(180) while ((Get-Date) -lt $deadline) { if (Test-Path $shot) { Start-Sleep -Seconds 2; break } if ($proc.HasExited) { break } Start-Sleep -Seconds 2 } $app = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue if ($app) { $app.CloseMainWindow() | Out-Null if (-not $app.WaitForExit(12000)) { $app | Stop-Process -Force } } $size = if (Test-Path $shot) { (Get-Item $shot).Length } else { 0 } $verdict = if ($size -ge $MinRenderedBytes) { 'RENDERED' } elseif ($size -gt 0) { 'BLANK' } else { 'NO-CAPTURE' } $results += [pscustomobject]@{ Pair = $pair; Arm = $arm.Label; Verdict = $verdict; Bytes = $size } Write-Host ("[ab-probe] pair {0}/{1} arm {2}: {3} ({4} bytes)" -f ` $pair, $Pairs, $arm.Label, $verdict, $size) Start-Sleep -Seconds 10 # let ACE clear the graceful logout before the next login } } Write-Host '' $results | Format-Table -AutoSize | Out-String | Write-Host foreach ($label in @($LabelA, $LabelB)) { $arm = @($results | Where-Object Arm -eq $label) $bad = @($arm | Where-Object Verdict -ne 'RENDERED') Write-Host ("[ab-probe] {0}: {1}/{2} blank" -f $label, $bad.Count, $arm.Count) } Write-Host ("[ab-probe] artifacts: {0}" -f $root)