feat(render): Campaign V slice V10 — Vulkan becomes the default backend
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>
This commit is contained in:
parent
49a309aaaf
commit
122fe8a7e2
8 changed files with 315 additions and 41 deletions
|
|
@ -66,7 +66,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| AD-46 | **Vulkan-arm only, dormant until the V10 cutover.** Dense alpha-blended distant scenery (the treeline) resolves differently on the Vulkan backend than on GL, by about 15% of the pixels in the band, even though both arms now request identical sampler state — trilinear, clamp-and-repeat, and the device's maximum anisotropy. What remains is the anisotropic TAP PATTERN, which both the GL and Vulkan specifications leave implementation-defined, and AMD's two drivers do not agree. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19 | Not assumed — narrowed by measurement, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame falls to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual is not a sub-pixel shift (an integer shift search finds none), not a sharpness change (high-frequency energy matches within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what makes it a driver property rather than a bug. | If it is NOT the tap pattern, some other Vulkan sampler or alpha-path difference is hiding behind this row, and it will present after cutover as distant foliage that shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value both arms now request |
|
||||
| AD-46 | **LIVE as of Campaign V slice V10 (cutover committed, user sign-off pending); previously Vulkan-arm-only and dormant.** Dense alpha-blended distant scenery (the treeline) resolves differently on the Vulkan backend than on GL, by about 15% of the pixels in the band, even though both arms now request identical sampler state — trilinear, clamp-and-repeat, and the device's maximum anisotropy. What remains is the anisotropic TAP PATTERN, which both the GL and Vulkan specifications leave implementation-defined, and AMD's two drivers do not agree. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19 | Not assumed — narrowed by measurement, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame falls to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual is not a sub-pixel shift (an integer shift search finds none), not a sharpness change (high-frequency energy matches within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what makes it a driver property rather than a bug. | If it is NOT the tap pattern, some other Vulkan sampler or alpha-path difference is hiding behind this row, and it will present after cutover as distant foliage that shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value both arms now request |
|
||||
| AD-38 | Outgoing teleport viewports retire when retail's quantized animation level exceeds the last captured visible level 1022 (index 96), suppressing levels 1023/1024 up to 20.2 ms before retail's literal `elapsed >= 1.0` state edge. Incoming fades retain the exact timer. | `src/AcDream.Core/World/TeleportAnimSequencer.cs` (`OutgoingViewportReachedTerminalProjection`) | An uncapped 2000 FPS pass can publish the finite tunnel at levels 1023/1024 even though the paired 2013 retail capture switches viewports after 1022. The table-level cutover preserves the captured visible viewport ordering without throttling the application. | Exit sound, viewport replacement, and logout tunnel entry can occur at most two easing-table quanta (about 20.2 ms) earlier than retail's logical timer. | `UIGlobals::GetAnimLevel @ 0x004EE540`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; paired retail/acdream captures documented in `docs/research/2026-07-15-retail-portal-space-pseudocode.md` |
|
||||
| AD-1 | Lost-cell machinery replaced by recoverable outdoor demote (**#107** safety net) + outdoor-restore `max(terrainZ, z)` under-terrain lift; retail goes `GotoLostCell` | `src/AcDream.Core/Physics/PhysicsEngine.cs:553` (+ :808) | acdream has no lost-cell state machine; outdoor landcell is the recoverable equivalent; the #107 auto-entry hold should make the demote branch unreachable | Gap in the hold → player committed to outdoor terrain inside/under a building (fake-grounded spawn, fall-through); a legit below-heightmap server restore is silently lifted — upward warp vs server | `GotoLostCell` pc:283418; `SetPositionInternal` 0x00515bd0, pc:283892-283945 |
|
||||
| AD-2 | Async readiness gates replace retail's synchronous destination cell load. **#229 refinement (2026-07-20):** login and F751 portal-space exit now share `WorldRevealReadinessBarrier`, so neither path can expose the normal viewport until the same render-publication, composite-texture, and collision domains converge. A hydratable indoor claim requires its owning Near-tier static/EnvCell mesh set, destination composites, and exact EnvCell physics (`IsSpawnCellReady`); an outdoor claim requires those render domains plus terrain/collision residency for the required Near ring. Hard-recenter generations and tier-aware completion application prevent stale overlapping loads/unloads or Far/Near jobs from opening or erasing the gate; mesh upload remains separate from balanced landblock ownership. Claims beyond NumCells still take the loud unhydratable-placement path. `RuntimeWorldTransitState` owns the shared reveal generation, accepted readiness, transit correlation, and exact generation/cell-scoped host-acknowledgement suffix. `WorldRevealCoordinator` is a graphical adapter holding only App resource receipts; normalized Runtime checkpoints observe ownership without defining another readiness path. **Slice E3 refinement (2026-07-24):** the same generation now publishes an immediate `WorldGenerationQuiescence` edge: old-world drawing/spatial queries, simulation/effect clocks, reconciliation, targeting, and 3-D audio stop while retained physical teardown advances through metered cursors and destination network/UI/streaming/readiness remain live. **Slice E4 refinement (2026-07-24):** accepted render/physics/static publication may span update frames through retained exact cursors, but reveal still consumes only the completed spatial/render-ready generation; building and EnvCell snapshots remain invisible until complete and the final spatial identity swap stays observer-atomic. **Slice E5 refinement (2026-07-24):** the reveal generation owns one exact destination reservation across every typed budget dimension. Stale completion cannot consume or clear its replacement, and hydratable incomplete content is never force-revealed; portal transit retains the DAT tunnel and centered retail wait cue until readiness converges. The hold→materialize→regain-control lifecycle remains owned by `TeleportAnimSequencer`. | `src/AcDream.Runtime/World/RuntimeWorldTransitState.cs`; `src/AcDream.App/Streaming/WorldRevealCoordinator.cs`; `src/AcDream.App/Streaming/WorldGenerationQuiescence.cs`; `src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs`; `src/AcDream.App/Streaming/StreamingOriginRecenterCoordinator.cs`; `src/AcDream.App/Streaming/LandblockPresentationPipeline.cs`; `src/AcDream.App/Streaming/StreamingController.cs`; `src/AcDream.App/Rendering/PortalTunnelPresentation.cs`; `src/AcDream.App/UI/PortalWaitNoticeController.cs`; `src/AcDream.App/Streaming/GpuWorldState.cs` (`IsRenderReady`); `src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs`; `src/AcDream.Core/Physics/PhysicsEngine.cs` (`IsSpawnCellReady`, `IsNeighborhoodTerrainResident`) | This is the asynchronous equivalent of retail leaving `SmartBox::position_update_complete` false while `CellManager::blocking_for_cells` is set: neither initial login nor portal arrival may reveal or continue simulating an old/partial collision world, a terrain-only Far shell, or a published-but-not-drawable GPU landblock. Indoor does not require a terrain heightmap, only the owning render landblock and exact EnvCell. | Gate opens early → grey/untextured first login or portal reveal, free-fall, wrong-cell rooting, missing scenery, or a still-active old generation; predicate never satisfies (streamer/DAT/upload failure) → login remains behind the world render gate, while portal transit remains in the authored tunnel and presents the centered wait cue after five seconds. | `SmartBox::UseTime` 0x00455410; `gmSmartBoxUI::UseTime` 0x004D6E30; `gmSmartBoxUI::EndTeleportAnimation` 0x004D65A0 |
|
||||
|
|
|
|||
|
|
@ -442,8 +442,11 @@ the report.
|
|||
## 5. Slice sequence
|
||||
|
||||
Every slice ends with `dotnet build` and the App test suite green, its gate
|
||||
passed, and one commit. GL remains the default backend through V9; all Vulkan
|
||||
work is dark behind `ACDREAM_RENDER_BACKEND` (default `gl`).
|
||||
passed, and one commit. GL remained the default backend through V9; all Vulkan
|
||||
work was dark behind `ACDREAM_RENDER_BACKEND` (default `gl`). **Slice V10
|
||||
inverted that**: the default is now `vulkan`, and `ACDREAM_RENDER_BACKEND=gl` is
|
||||
the escape hatch for one slice — see §5.5.23, and note that the cutover is
|
||||
committed but not yet signed off.
|
||||
|
||||
**Run the suite in Release: `dotnet test … -c Release`.** Some tests assert
|
||||
Release-only behaviour and legitimately fail in Debug —
|
||||
|
|
@ -620,7 +623,7 @@ tenth pair with no consumer at all; see the V6e report.
|
|||
| **V7** ◐ **partially discharged — §5.5.19** | GL-versus-Vulkan differential: `tools/run-backend-differential-gate.ps1` (**built at V6m**), strict paired-PNG compare, divergences fixed in the Vulkan backend only, then lifecycle + R6 soak natively on Vulkan, one validation-layer-clean run, one RenderDoc capture. **Milestone: parity.** Starting distance, measured at V6m: 18.52% of the frame at the first stop. **What landed:** the world atlases' missing anisotropy (`ad5f8b68`, retail-anchored at `0x005a4230`), and two instrument pins the gate was silently missing — the cloud sheet's phase and, much larger, the Dereth clock, which had never actually been pinned by anything and was moving 22% of the frame between two captures 45 s apart *in the same run*. Offline GL-versus-Vulkan, both clocks pinned, is now **8.82e-04 below the tree band — inside the threshold**; the treeline is `AD-46`, proven not to be the depth class. **What did NOT land:** a passing connected stop (each carries a named phase exception), per-stop masks in the gate script, an aperture stop for the portal depth mask, ~~the R6 soak on Vulkan~~ (**run and passed at V8 — §5.5.21**), and the RenderDoc capture (**V8 established the cause: RenderDoc is not installed on this machine**). | every differential checkpoint passes; both connected routes green on VK |
|
||||
| **V8** ⏸ **measured; two floors missed; the cutover call is the user's — §5.5.21** | Perf gate on the RX 9070 XT, uncapped, both backends, same scene, same day. **What landed:** `VulkanFrameGpuMeasurement` (`00e1b321`), without which the Vulkan arm emitted no `[frame-prof]` line at all — `NullRenderFrameGpuMeasurement` was the only caller of `FrameProfiler.FrameBoundary`, so no performance vehicle could be pointed at it; the finding that **the R6 soak is NOT the vehicle §2's founding numbers came from and is biased against Vulkan** by the per-frame swapchain copy its own artifact directory arms; a same-day GL-versus-Vulkan profile on the G5 ordinary-production vehicle; a phase-level CPU attribution on both arms; and **the R6 soak run natively on Vulkan** (PASS, 0 failures, graceful exit), which V7 left outstanding. **Result, in three configurations:** on a stationary LIGHT scene at 780-870 FPS Vulkan is 75.4% cheaper on GPU p50 and 85.3% lighter on allocation but **14.8% more expensive on CPU p50 and 8.8% on p99** — two floors missed; on a stationary DENSE scene (21,024 entities, identical on both arms) **Vulkan wins every row**, including CPU p50/p99 and 18.5% less total process CPU by Windows' accounting; and on the nine-stop route against an identical world **Vulkan wins every row** and renders **27.3% more frames**. The Vulkan-specific cost is **fixed per frame** — 0.148 ms of required WSI/sync calls (present 0.070, submit 0.027, timeline wait 0.026, acquire 0.025) against GL's ~0.014 ms of present — so it dominates an almost-empty frame and disappears into a full one. **The campaign's named cost centre is closed as measured-and-not-worth-it:** bindings 4/6/7/8 cost **0.031 ms for all ~216 draws of the frame**, 2.4% of it. No Vulkan code was changed to chase the miss. **Not taken:** the RenderDoc capture — RenderDoc is not installed on this machine; it carries to V10. | §2 acceptance table; parity is the floor |
|
||||
| **V9** ✅ **CI green — §5.5.20, §5.5.22** | Linux + CI: a `linux-vulkan` job on lavapipe (probe accepts on a real 1.3+ software device, a short real render under xvfb, forced-unsupported → exit 4, `.spv` freshness). **What landed:** the eleven-step job; `ACDREAM_VULKAN_PROBE_FRAMES`, without which the harness cannot terminate unattended; `tools/compile-shaders.ps1` made path-portable; and the report's jq contract pinned by App tests so a rename fails locally rather than in CI. lavapipe clears every gate requirement by source inspection, including the `samplerAnisotropy` V7 made load-bearing. **Deferred:** the physical Linux GPU row, post-cutover, as for Slice L; and Wayland, which no runner offers. **Not attempted, with cause:** a GL-versus-Vulkan pixel comparison — the GL job asserts exit 4 and so has no frame, and the probe renders synthetic scenes rather than the DAT world CI cannot have. | CI green including the new job — **met**: run 30393357552, all four jobs green, lavapipe `llvmpipe (Cpu)` at Vulkan 1.4.318 / Mesa 25.2.8, a 1280x720 / 35,594-byte captured frame, and "all committed .spv match a fresh compile" on Linux |
|
||||
| **V10** | Cutover: Vulkan default, GL reachable by env var for one slice, gate scripts default to VK. | complete Release suite + retail expected PNGs **on VK** (baselines not regenerated) + both connected routes + **user visual sign-off** |
|
||||
| **V10** ◐ **flipped and measured; PENDING THE USER'S SIGNATURE — §5.5.23** | Cutover: Vulkan default, GL reachable by env var for one slice, gate scripts default to VK. **What landed:** `ParseRenderBackend` inverted (unset/typo → Vulkan; only `gl`/`opengl` → OpenGL, the polarity of the typo case flipping with the default because GL is now the backend V11 deletes); `run-offline-pixel-gate.ps1` gained `-Backend` (default `vulkan`) and forces all four determinism levers plus MSAA off instead of inheriting them; the two connected gates **clear** `ACDREAM_RENDER_BACKEND` so they exercise the process default and an ambient override cannot disguise a GL run as one. **Battery:** complete Release suite **9,222 / 5 skipped / 0 failed** (`#250` family also 4/4 singly); repeat connected gate **3/3 on both columns**; connected world-lifecycle route **PASS**, 0 failures, both sessions graceful at exit 0; validation layer proven inserted at instance and device level with **zero errors and zero warnings**; GL escape hatch verified by two offline launches. **Every connected launch reached Vulkan with no environment variable set.** **The pixel gate is NOT met and was not relaxed:** VK against the GL-era capture is **1.099e-03 masked / 3.764e-02 whole-frame**, 97.9% of it in the treeline band, the masked residual entirely on distant alpha-blended scenery silhouettes — `AD-46`, whose register row this slice moves from dormant to live. Below the band the arms are photometrically identical (mean luminance Δ 0.01 of 255). Nothing GL, ImGui or Studio is deleted; that stays V11's. **Rollback: `git revert` of this slice's commit.** | complete Release suite + retail expected PNGs **on VK** (baselines not regenerated) + both connected routes + **user visual sign-off** |
|
||||
| **V11** | GL deletion and closeout: delete `Gpu/Gl`, `OpenGLGraphicsDevice`, `ManagedGL*`, `GLSLShader`, `GLHelpers`, `GLStateScope`, `RenderStateCache`, `BindlessSupport`, `GraphicalGlFunctionProbe`, the GL branch in `GameWindow`, the ImGui project and Studio; drop the GL and (if the audit is clean) Chorizite packages; file the retained-UI dev-panels follow-up; swap CI assertions to VK; update the divergence register, architecture doc, code-structure doc, and rendering memory crib; re-measure memory. | complete Release suite + both connected routes + working-set re-measure |
|
||||
|
||||
### 5.2 Why V2's handle table is not the device's table
|
||||
|
|
@ -3336,6 +3339,140 @@ ever exercised a cache hit. 10/10 clean under the pin. Filed as #255.
|
|||
**Net effect on the V9 row:** the job is green, and so is the whole workflow —
|
||||
its first fully green run is the evidence the row was waiting for.
|
||||
|
||||
#### 5.5.23 V10 (2026-07-28): the default is Vulkan, and the signature is outstanding
|
||||
|
||||
**This slice does not complete V10.** It flips the default and runs the battery so
|
||||
that the one remaining acceptance criterion — the user looking at the client and
|
||||
saying it matches retail — has evidence in front of it. §7 names the V10 visual
|
||||
sign-off as the campaign's only required user stop besides gate failures, and it
|
||||
has not been given. **The cutover is committed but unsigned, and reverting this
|
||||
slice's commit restores the GL default, the old escape-hatch polarity and the gate
|
||||
scripts' inherited backend in one step.**
|
||||
|
||||
**What changed, and only this.** `RuntimeOptions.ParseRenderBackend` inverted: an
|
||||
unset, empty or unrecognised `ACDREAM_RENDER_BACKEND` now yields
|
||||
`RenderBackendKind.Vulkan`, and only `gl` or `opengl` (case-insensitive) selects
|
||||
OpenGL. The polarity of the typo case flipped with the default, deliberately and
|
||||
for the reason it had before read the other way round: a misspelling must never
|
||||
silently start the backend that cannot carry the client. Before V10 that was
|
||||
Vulkan, because it was dark; after V10 it is GL, because V11 deletes it. Five
|
||||
test cases replace two in `RuntimeOptionsTests`, and the pin of the old default
|
||||
is the only test this slice touches.
|
||||
|
||||
Three gate scripts follow. `run-offline-pixel-gate.ps1` gained `-Backend`
|
||||
(default `vulkan`) and now **forces** all four determinism levers — backend, day
|
||||
group, world day fraction, sky phase — plus `ACDREAM_MSAA_SAMPLES=0`, rather than
|
||||
inheriting any of them. `run-repeat-connected-gate.ps1` and
|
||||
`run-connected-world-lifecycle-gate.ps1` **clear** `ACDREAM_RENDER_BACKEND`
|
||||
instead of 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. Nothing GL, ImGui or Studio is deleted; that is V11's, untouched.
|
||||
|
||||
---
|
||||
|
||||
**The battery.**
|
||||
|
||||
| Gate | Result |
|
||||
|---|---|
|
||||
| Complete Release suite | **9,222 passed / 5 skipped / 0 failed** across 9 projects |
|
||||
| `#250` family, run singly | **4/4 pass** (none failed in the whole-suite run either) |
|
||||
| GL escape hatch, offline | **works** — `ACDREAM_RENDER_BACKEND=gl` reports `4.3.0 Core Profile Context`, bindless present, 1,730,800 B frame, exit 0. Twice. |
|
||||
| Repeat connected gate, `-Runs 3` | **PASS 3/3 on both columns** (attempt 2; attempt 1 is below) |
|
||||
| Connected world-lifecycle route | **PASS** — 0 failures, 1 warning (`capped: 25 expected world-edge landblock miss(es)`); both sessions exited gracefully with code 0 (239 s capped, 61 s uncapped reconnect), six canonical capped checkpoints plus the reconnect checkpoint |
|
||||
| Validation layer proven loaded | **zero errors, zero warnings** — loader prints `Insert instance layer "VK_LAYER_KHRONOS_validation"` and `Inserted device layer`, and the run captured a real 1,753,854 B frame |
|
||||
| Offline pixel gate, VK against the GL-era capture | **1.099e-03 masked / 3.764e-02 whole-frame — over threshold, and the excess is `AD-46` alone.** Detail below. |
|
||||
|
||||
The suite number is +5 against the pre-flip tree's 9,217/5, and all five are this
|
||||
slice's own escape-hatch cases; the App project moved 4,152 → 4,157 exactly.
|
||||
**Every connected launch in the battery reached Vulkan with no environment
|
||||
variable set** — six repeat-gate runs and both lifecycle sessions log
|
||||
`vulkan: capability gate passed`, which is the flip itself under test rather than
|
||||
an assertion about it.
|
||||
|
||||
---
|
||||
|
||||
**The pixel number, honestly.** No baseline was regenerated, and none exists to
|
||||
regenerate: the campaign commits no expected PNGs (§5.1 captures the left-hand
|
||||
side at a reference commit instead), so the GL-era expected frame was taken at
|
||||
this same commit through the escape hatch — which is also what verifies the hatch.
|
||||
Both captures: 1280x720, MSAA off, day group 0, day fraction 0.5, sky phase 0.
|
||||
|
||||
| Pair | Whole frame | Top 280 masked |
|
||||
|---|---|---|
|
||||
| GL vs GL, same binary (control) | 1,011 / 921,600 = **1.097e-03** | 10 / 563,200 = **1.78e-05** |
|
||||
| VK vs VK, same binary (control) | 482 / 921,600 = **5.23e-04** | 8 / 563,200 = **1.42e-05** |
|
||||
| **GL vs VK** | 34,690 = **3.764e-02** | 619 = **1.099e-03** |
|
||||
| GL vs VK, second independent pair | 34,649 = 3.760e-02 | 613 = 1.088e-03 |
|
||||
|
||||
Three things that table says and a bare "it failed" would not.
|
||||
|
||||
1. **97.9% of the whole-frame difference is in rows 0-239** — the treeline and
|
||||
the sky behind it. This scene is the Holtburg overlook, whose top third is
|
||||
solid conifer billboards.
|
||||
2. **The masked residual is 619 px against a same-backend control of 10 px**, so
|
||||
it is not capture noise; and every one of those 619 pixels sits on the
|
||||
silhouette of a distant alpha-blended scenery clump, verified by inspecting the
|
||||
difference map rather than inferred. That is `AD-46`'s registered population
|
||||
exactly — the anisotropic tap pattern in dense alpha-blended scenery, which
|
||||
both specifications leave implementation-defined. §5.5.19 measured the same
|
||||
quantity at 497 px / 8.8e-04 on its own capture; this one is 619 px /
|
||||
1.099e-03. **Same class, 10% over the threshold instead of 12% under it.**
|
||||
3. **Below the band the two backends are photometrically identical**: mean
|
||||
luminance differs by **0.01 of 255** over rows 280-719, and 612 pixels of
|
||||
563,200 differ at all. In the band the Vulkan arm is slightly **darker** — mean
|
||||
luminance -1.93 of 255 across rows 0-279, -4.71 in a tight treeline crop, with
|
||||
74% of differing pixels darker on Vulkan. So the foliage reads marginally
|
||||
denser, which is the exact symptom `AD-46`'s risk column predicted. At 1:1 the
|
||||
two full frames are not distinguishable by eye; at 4x the fringes are.
|
||||
|
||||
`AD-46`'s register row is updated in this commit from "dormant until the V10
|
||||
cutover" to live, which is the deviation this slice introduces.
|
||||
|
||||
**The gate as written is not met, and this slice does not relax it.** §7.1 rule 2
|
||||
forbids widening the mask or the tolerance to turn this green, and neither was
|
||||
touched: the mask stayed at the 280 rows it has had since the gate was built, and
|
||||
the tolerance at 2 / 0.001. What the slice does instead is put the split, the
|
||||
control and the population in front of the person whose sign-off V10 needs.
|
||||
|
||||
---
|
||||
|
||||
**Two findings from running the battery, both about instruments.**
|
||||
|
||||
**1. The offline pixel gate's sky mask is still load-bearing, and V10 nearly
|
||||
retired it on an assumption.** The reasoning looked sound — V7 found the two
|
||||
clocks that make the sky drift, this script now pins both, therefore the mask is
|
||||
obsolete and masking would only hide regressions — so the default was changed to
|
||||
0. The control refuted it: with all four levers pinned, two launches of the *same
|
||||
binary* still differ by 1,011 px on GL and 482 px on Vulkan, essentially all of it
|
||||
in the band, which means an unmasked self-differential fails the 0.001 threshold
|
||||
on noise alone. The default was put back to 280 and the measurement written into
|
||||
the script's help so the next reader does not repeat the reasoning. Below the
|
||||
band, the same controls show 10 px and 8 px — the strictness the mask buys.
|
||||
|
||||
**2. The repeat-gate desktop witness needs an uncontested primary monitor, and
|
||||
said so all along.** Attempt 1 reported 1/3, with runs 2 and 3 "BLANK" at ~420 KB
|
||||
while the client's own capture rendered at ~1.38 MB — the two instruments
|
||||
disagreeing, which that script documents as itself a finding. The grabs settle it
|
||||
with no ambiguity: run 2's PNG is a **web browser**, run 3's is **Discord**.
|
||||
`CopyFromScreen` captured whatever was composited at the client's client-rect,
|
||||
which is failure mode one in the script's own header. Attempt 2, minutes later,
|
||||
passed 3/3 on both columns. The client's Vulkan frame was correct in all six runs.
|
||||
|
||||
Worth recording for V11, which inherits this instrument: the §5.5.2 finding that
|
||||
justified making the desktop grab the verdict — a blank GL frame reads back from
|
||||
framebuffer 0 as RGBA(0,0,0,0) even where the UI is demonstrably on screen — is a
|
||||
statement about **GL's** readback. The Vulkan arm captures by copying the
|
||||
swapchain image on the device (`RecordBackbufferCapture`), which is not that path.
|
||||
Whether the client capture is now strong enough to be a verdict rather than a
|
||||
second column is a question for whoever next touches the gate; this slice only
|
||||
notes that the reason it was demoted no longer obviously applies.
|
||||
|
||||
---
|
||||
|
||||
**Not taken, and why.** The RenderDoc capture the V7 and V8 lists carried is still
|
||||
outstanding: V8 established the cause — RenderDoc is not installed on this machine
|
||||
— and nothing about the cutover changes that. It carries to V11.
|
||||
|
||||
### 5.4 The null-target `BeginPass` divergence (V4c) — ✅ DISCHARGED at V6k
|
||||
|
||||
> **Closed 2026-07-28 by V6k commit 2 (`eb7e6b4e`); see §5.5.16.** The answer is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue