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:
parent
543bc79f8a
commit
61f3c5d803
2 changed files with 152 additions and 0 deletions
|
|
@ -603,6 +603,40 @@ as well, and the `SceneLighting` UBO (binding 1) by terrain and the four viewpor
|
|||
portal renderers. GL binding points are global, so the safe move while those consumers
|
||||
are still raw GL is to leave both bound as they are and convert them with the spine.
|
||||
|
||||
### 5.5 The V4c/V4d revert (2026-07-27) and the re-land conditions
|
||||
|
||||
V4c and V4d were reverted at `543bc79f`/`b537f3a9`/`ad61f250` after the first
|
||||
connected sessions showed a **blank world** — UI rendered, sounds played, the log
|
||||
was clean, `world-reveal` reported `visible=True`, and the user hit one AMD driver
|
||||
timeout. The offline gate had passed both slices at noise level.
|
||||
|
||||
What the debugging established, with the connected screenshot-probe apparatus:
|
||||
the defect is **intermittent (~1 in 3 at the worst location, 0 of 7 at the V4c
|
||||
parent)** and scene-dependent; after the first world frame the default
|
||||
framebuffer's colour reads return garbage (float depth bytes) and `glClear`
|
||||
stops having any effect, with `glGetError` clean throughout — a GPU-side fault,
|
||||
not an API error. Every added CPU↔GPU sync point monotonically suppresses it,
|
||||
which also made the mechanism unprovable in situ. Best-supported cause: the
|
||||
frame ring performs 10–40 partial `glBufferSubData` updates per frame into a
|
||||
buffer object that already-submitted same-frame draws are still reading; the
|
||||
offline flat path issues 2–4 such updates, the connected PView path dozens —
|
||||
which is exactly the offline/connected axis. This likely also explains the TDR.
|
||||
|
||||
The contract amendments (`111e7236` InverseAlpha, `c7f5f251` integer vertex
|
||||
attributes + tiling binding) were **kept** — they are dark, test-covered, and
|
||||
correct.
|
||||
|
||||
**Re-land conditions, binding:**
|
||||
1. The GL ring's write path moves to `glMapBufferRange(WRITE | UNSYNCHRONIZED |
|
||||
INVALIDATE_RANGE)` — the canonical GL ring idiom, which states the non-overlap
|
||||
invariant to the driver instead of leaving `glBufferSubData`-into-an-in-use-
|
||||
buffer to driver heuristics.
|
||||
2. V4c and then V4d re-land as reverts-of-the-reverts plus the ring change, each
|
||||
gated by `tools/run-repeat-connected-gate.ps1` at **10/10 rendered** — a single
|
||||
connected run passes a broken binary ~70% of the time and gates nothing.
|
||||
3. The offline pixel gate still passes, and the gate location stays pinned
|
||||
(failure rate is location-sensitive; stray input moves the character).
|
||||
|
||||
### 5.4 The null-target `BeginPass` divergence (V4c) — must be undone at V6
|
||||
|
||||
V4c had to stop GL's `BeginPass` from binding framebuffer 0 when a pass declares
|
||||
|
|
|
|||
118
tools/run-repeat-connected-gate.ps1
Normal file
118
tools/run-repeat-connected-gate.ps1
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue