THIS CUTOVER AWAITS THE USER'S VISUAL SIGN-OFF. It is not complete. Section 7
of the campaign plan names the V10 sign-off as the only required user stop
besides gate failures, and it has not been given. This commit flips the default
and runs the battery so that the sign-off has evidence in front of it.
ROLLBACK, one line: `git revert` of this commit. It restores the GL default,
the pre-V10 escape-hatch polarity and the gate scripts' inherited backend
together; nothing else has to move with it.
An unset, empty or unrecognised ACDREAM_RENDER_BACKEND now yields
RenderBackendKind.Vulkan. Only `gl` or `opengl`, case-insensitive, selects
OpenGL. The polarity of the typo case flipped with the default and on purpose:
before V10 an unrecognised token had to land on GL because Vulkan was dark and a
typo must never silently start a backend that cannot draw; after V10 it has to
land on Vulkan for the same reason read the other way, because GL is the backend
V11 deletes. `opengl` is honoured beside `gl` because an escape hatch exists to
be found.
Three gate scripts follow the flip. run-offline-pixel-gate.ps1 gains -Backend
(default vulkan) and now FORCES all four determinism levers — backend, day
group, world day fraction, sky phase — plus ACDREAM_MSAA_SAMPLES=0, instead of
inheriting any of them. run-repeat-connected-gate.ps1 and
run-connected-world-lifecycle-gate.ps1 CLEAR ACDREAM_RENDER_BACKEND rather than
setting it, so what they exercise is the process default and an ambient override
in a caller's shell cannot make a GL run wear the default's report.
TEST PIN UPDATED, flagged as required: RenderBackend_DefaultsToGl becomes
RenderBackend_DefaultsToVulkan, and RenderBackend_AnythingElseStaysOnGl splits
into RenderBackend_SelectsGlOnlyForTheEscapeHatchTokens and
RenderBackend_AnythingElseStaysOnVulkan. Five cases replace two. No other test
is touched, weakened or deleted.
AD-46's divergence-register row moves from "dormant until the V10 cutover" to
live, in this commit, per the same-commit register rule.
Battery, all on the new default:
complete Release suite 9,222 passed / 5 skipped / 0 failed (9 projects)
+5 against the pre-flip 9,217; the +5 are this
slice's own escape-hatch cases
#250 family, singly 4/4 pass (none failed in the whole-suite run)
repeat connected gate PASS 3/3 on both columns
world-lifecycle route PASS, 0 failures, both sessions graceful at exit 0
validation layer inserted at instance AND device level by the loader,
zero errors and zero warnings, real frame captured
GL escape hatch verified by two offline launches: 4.3.0 Core Profile
Context, bindless present, exit 0
Every connected launch in the battery reached Vulkan with no environment
variable set, which is the flip itself under test rather than an assertion
about it.
THE PIXEL GATE IS NOT MET, AND WAS NOT RELAXED. Vulkan against a GL-era capture
taken at this commit through the escape hatch, MSAA off and both clocks pinned:
1.099e-03 masked / 3.764e-02 whole-frame, against a 0.001 threshold. 97.9% of
the difference is in the treeline band, and the masked residual of 619 px — set
against a same-backend control of 10 px — sits entirely on the silhouettes of
distant alpha-blended scenery. That is AD-46's registered population; section
5.5.19 measured the same quantity at 497 px / 8.8e-04. Below the band the two
backends are photometrically identical: mean luminance differs by 0.01 of 255.
No baseline was regenerated and no mask or tolerance was widened.
Two instrument findings are recorded in section 5.5.23. The offline gate's sky
mask is still load-bearing — this slice tried retiring it on the reasoning that
V7's clock pins had made it obsolete, and the control refuted that: two launches
of the same binary still differ by 1,011 px on GL and 482 px on Vulkan, almost
all of it in the band. The default went back to 280 with the measurement written
into the script's help. And the repeat gate's desktop witness needs an
uncontested primary monitor: a first attempt reported 1/3, and the two failing
grabs turn out to be a web browser and Discord composited over the client rect,
not a blank frame — the client's Vulkan capture rendered in all six runs.
Nothing GL, ImGui or Studio is deleted. That is V11's scope and it is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
202 lines
9.1 KiB
PowerShell
202 lines
9.1 KiB
PowerShell
<#
|
|
.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 -> desktop grab -> graceful
|
|
close -> cooldown. The teleloc is pinned because the failure rate is
|
|
location-sensitive.
|
|
|
|
THE VERDICT IS THE DESKTOP GRAB, not the client's own screenshot. Campaign
|
|
plan section 5.5.2 established that on a blank run the client's read of
|
|
framebuffer 0 returns RGBA(0,0,0,0) in every pixel INCLUDING pixels where the
|
|
UI is demonstrably on screen - so a verdict derived from screenshot bytes is
|
|
reporting the readback apparatus, not the renderer. This script therefore
|
|
launches the window visible, brings it to the foreground, and grabs the
|
|
composited client rectangle with CopyFromScreen: a witness that shares
|
|
nothing with the renderer below the compositor. The client's own capture is
|
|
still recorded, as a second column, because a disagreement between the two is
|
|
itself a finding.
|
|
|
|
Observed magnitudes at 1280x720: a rendered desktop grab is ~1.6 MB, a blank
|
|
one (atmosphere clear plus the complete retained UI, no 3-D) is ~0.23 MB, and
|
|
a blank client capture is ~5 KB. One threshold separates all three.
|
|
|
|
Run this with nothing else on the primary monitor: CopyFromScreen captures
|
|
whatever is composited at those coordinates, so an overlapping window would be
|
|
captured instead of the client. A visible foregrounded window can also take
|
|
stray input, which moves the character off the pinned cell - do not type
|
|
while the gate is running.
|
|
|
|
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
|
|
PNG size below which an image is called blank. Applied to both the desktop
|
|
grab (the verdict) and the client capture (recorded only). 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.'
|
|
}
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
Add-Type @'
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
public static class RepeatGateSurface {
|
|
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
[DllImport("user32.dll")] public static extern bool GetClientRect(IntPtr hWnd, out RECT r);
|
|
[DllImport("user32.dll")] public static extern bool ClientToScreen(IntPtr hWnd, ref POINT p);
|
|
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; }
|
|
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X, Y; }
|
|
}
|
|
'@
|
|
|
|
$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 20000
|
|
"@
|
|
|
|
$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
|
|
# Campaign V slice V10: deliberately cleared rather than set. The blank-world
|
|
# defect this gate exists for is a property of whichever backend the client
|
|
# actually starts, so the gate must launch the process default -- Vulkan
|
|
# after the cutover -- and an ambient override from the caller's shell would
|
|
# silently make it a GL run wearing the default's report.
|
|
Remove-Item Env:\ACDREAM_RENDER_BACKEND -ErrorAction SilentlyContinue
|
|
|
|
$log = Join-Path $dir 'client.log'
|
|
# Normal window style: the frame must be composited on screen to be grabbed.
|
|
$proc = Start-Process -FilePath $ExePath -RedirectStandardOutput $log `
|
|
-RedirectStandardError "$log.err" -PassThru
|
|
|
|
$shot = Join-Path $dir 'screenshots\repeat-run.png'
|
|
$grab = Join-Path $dir 'desktop-grab.png'
|
|
$deadline = (Get-Date).AddSeconds(180)
|
|
while ((Get-Date) -lt $deadline) {
|
|
if (Test-Path $shot) { break }
|
|
if ($proc.HasExited) { break }
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
|
|
if (Test-Path $shot) {
|
|
# The client is now in its trailing sleep, still rendering this scene,
|
|
# so both images observe one steady frame.
|
|
Start-Sleep -Milliseconds 800
|
|
try {
|
|
$proc.Refresh()
|
|
$h = $proc.MainWindowHandle
|
|
if ($h -ne [IntPtr]::Zero) {
|
|
[RepeatGateSurface]::ShowWindow($h, 9) | Out-Null # SW_RESTORE
|
|
[RepeatGateSurface]::SetForegroundWindow($h) | Out-Null
|
|
Start-Sleep -Milliseconds 1200
|
|
$r = New-Object RepeatGateSurface+RECT
|
|
[RepeatGateSurface]::GetClientRect($h, [ref]$r) | Out-Null
|
|
$p = New-Object RepeatGateSurface+POINT
|
|
[RepeatGateSurface]::ClientToScreen($h, [ref]$p) | Out-Null
|
|
$w = $r.R - $r.L; $ht = $r.B - $r.T
|
|
if ($w -gt 0 -and $ht -gt 0) {
|
|
$bmp = New-Object System.Drawing.Bitmap $w, $ht
|
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
$g.CopyFromScreen($p.X, $p.Y, 0, 0, (New-Object System.Drawing.Size $w, $ht))
|
|
$bmp.Save($grab, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
$g.Dispose(); $bmp.Dispose()
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host "[repeat-gate] desktop grab failed: $_"
|
|
}
|
|
}
|
|
|
|
$app = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue
|
|
if ($app) {
|
|
$app.CloseMainWindow() | Out-Null
|
|
if (-not $app.WaitForExit(12000)) { $app | Stop-Process -Force }
|
|
}
|
|
|
|
$grabSize = if (Test-Path $grab) { (Get-Item $grab).Length } else { 0 }
|
|
$shotSize = if (Test-Path $shot) { (Get-Item $shot).Length } else { 0 }
|
|
$verdict = if ($grabSize -ge $MinRenderedBytes) { 'RENDERED' }
|
|
elseif ($grabSize -gt 0) { 'BLANK' }
|
|
else { 'NO-CAPTURE' }
|
|
$clientView = if ($shotSize -ge $MinRenderedBytes) { 'RENDERED' }
|
|
elseif ($shotSize -gt 0) { 'BLANK' }
|
|
else { 'NO-CAPTURE' }
|
|
$results += [pscustomobject]@{
|
|
Run = $i; Verdict = $verdict; GrabBytes = $grabSize
|
|
ClientCapture = $clientView; ClientBytes = $shotSize
|
|
}
|
|
Write-Host ("[repeat-gate] run {0}/{1}: screen={2} ({3} B) client={4} ({5} B)" -f `
|
|
$i, $Runs, $verdict, $grabSize, $clientView, $shotSize)
|
|
|
|
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
|
|
$split = @($results | Where-Object { $_.Verdict -ne $_.ClientCapture })
|
|
if ($split.Count -gt 0) {
|
|
Write-Host ("[repeat-gate] note: {0}/{1} runs had the two instruments disagree." -f `
|
|
$split.Count, $Runs)
|
|
}
|
|
if ($blank.Count -gt 0) {
|
|
Write-Host ("[repeat-gate] FAILED: {0}/{1} runs did not render on the desktop witness. Artifacts: {2}" -f $blank.Count, $Runs, $root) -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
Write-Host ("[repeat-gate] PASS: {0}/{0} runs rendered on the desktop witness. Artifacts: {1}" -f $Runs, $root)
|
|
exit 0
|