test(render): add the repeat-run connected gate; record V4c/V4d re-land conditions

The V4c blank-world regression was intermittent - roughly one launch in three at the worst location, zero in seven at the parent - so a single connected capture passes the broken binary most of the time and gates nothing. The new gate runs N full connect-teleloc-render-screenshot cycles with graceful logout and a per-run verdict by screenshot content size, refuses to start if a client is already using the shared test account, and pins the teleloc because the failure rate is location-sensitive. Ten clean runs bound a one-in-three defect below roughly four percent.

Campaign doc 5.5 records the revert evidence and the binding re-land conditions: the GL ring write path moves to mapped unsynchronized writes, and V4c/V4d re-land only at 10/10 rendered plus a passing offline gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-27 22:43:03 +02:00
parent 543bc79f8a
commit 61f3c5d803
2 changed files with 152 additions and 0 deletions

View file

@ -0,0 +1,118 @@
<#
.SYNOPSIS
Campaign V repeat-run connected gate: N full connect-render-screenshot cycles
against the local ACE server, with a pass/fail verdict per run.
.DESCRIPTION
The V4c blank-world regression was INTERMITTENT: roughly 1 launch in 3 at the
worst location, 0 in 7 at the parent commit. A single connected capture is
therefore worthless as a gate for this defect class - the broken binary
passes it most of the time. This script exists so no future slice can call
itself connected-clean on one lucky run.
Each run: launch connected -> teleloc to the pinned worst-case cell ->
wait for world-visible -> settle -> screenshot -> graceful close -> cooldown.
The verdict is by screenshot content size: a rendered 1280x720 world compresses
to ~1.5 MB; the blank-world failure produces a few-KB flat PNG. The teleloc is
pinned because the failure rate is location-sensitive, and stray input into a
minimized window silently moves the character between runs.
Graceful close matters: a hard kill leaves ACE holding the session ~3 minutes
and every subsequent run fails with 'CharacterList not received'.
.PARAMETER Runs
Cycles to run. Default 10. The defect's observed rate was ~1/3, so ten clean
runs bound it below ~4% at 90% confidence - adequate for a re-land gate.
.PARAMETER ExePath
Client executable. Defaults to this repo's Release output.
.PARAMETER Teleloc
The pinned location, as the full /teleloc argument string. Default is the
cell with the worst observed failure rate (5/5 blank at the V4c HEAD).
.PARAMETER MinRenderedBytes
Screenshot size below which a run is called blank. Default 500000.
#>
[CmdletBinding()]
param(
[int]$Runs = 10,
[string]$ExePath,
[string]$Teleloc = "0x2E430012 57.895 42.116 16.802 1 0 0 0",
[int]$MinRenderedBytes = 500000
)
$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent $PSScriptRoot
if (-not $ExePath) { $ExePath = Join-Path $repo 'src\AcDream.App\bin\Release\net10.0\AcDream.App.exe' }
if (-not (Test-Path $ExePath)) { throw "Client not found at $ExePath." }
# 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 gate uses the shared test account and must not steal its session.'
}
$root = Join-Path $env:TEMP "claude\repeat-connected-$([DateTime]::Now.ToString('HHmmss'))"
New-Item -ItemType Directory -Force -Path $root | Out-Null
$results = @()
for ($i = 1; $i -le $Runs; $i++) {
$dir = Join-Path $root "run-$i"
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 repeat-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 $ExePath -RedirectStandardOutput $log `
-RedirectStandardError "$log.err" -PassThru -WindowStyle Minimized
$shot = Join-Path $dir 'screenshots\repeat-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]@{ Run = $i; Verdict = $verdict; Bytes = $size }
Write-Host ("[repeat-gate] run {0}/{1}: {2} ({3} bytes)" -f $i, $Runs, $verdict, $size)
Start-Sleep -Seconds 10 # let ACE clear the graceful logout before the next login
}
Write-Host ''
$blank = @($results | Where-Object Verdict -ne 'RENDERED')
$results | Format-Table -AutoSize | Out-String | Write-Host
if ($blank.Count -gt 0) {
Write-Host ("[repeat-gate] FAILED: {0}/{1} runs did not render. Artifacts: {2}" -f $blank.Count, $Runs, $root) -ForegroundColor Red
exit 1
}
Write-Host ("[repeat-gate] PASS: {0}/{0} runs rendered. Artifacts: {1}" -f $Runs, $root)
exit 0