docs(render): record V6j - the world arm, the winding measurement, and the V7 list

Section 5.5.15 and the slice-table row. Three things worth having written down
rather than rediscovered.

The winding inversion V6c wrote was wrong and had never been asked a question:
every Vulkan consumer through V6i sets Cull = None, so the mapping had not decided
a fragment until the world arm arrived. That makes three defects this campaign has
found in a path that compiled, validated clean, and had a test - the descriptor
layouts and the TerrainClip set were the first two - and all three share the shape
of a test that asserts the behaviour rather than the requirement.

The A8 CullMode.Landblock override, which sections 5.5.13 and 5.5.14 both flagged
as due for an answer the moment world materials drew on a second backend, has one:
it carried over verbatim and is still load-bearing, so it is now a divergence with
two consumers rather than one. The dungeon pass in section 5.1's checklist is what
settles it, not this slice.

And the V7 defect list, so the differential is not run against a target that
cannot pass it: section 5.4's null-target divergence is still live on GL,
EnvCellRenderer's Vulkan arm and the deferred-alpha and doorway-scissor paths are
unexercised by the offline scene, and MSAA must come off.

The pixel-gate figures are recorded as a distribution rather than a number,
because 31 differing pixels sits at the documented band's top and a single value
there is not evidence either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-28 15:49:46 +02:00
parent f84eef3256
commit 7ae796a110

View file

@ -576,6 +576,7 @@ tenth pair with no consumer at all; see the V6e report.
| **V6h** ✅ | **The Vulkan composition host**, specified by §5.5.9 and reported in §5.5.10. `ACDREAM_RENDER_BACKEND=vulkan` runs the real `GameWindow` composition — DAT load, streaming, camera, entity table, session, and the real retained `UiHost` through the RHI — with no world renderers. Three seams: the already-generic platform acquisition now publishes a `GameWindowGraphics`; `VulkanHostInputCameraCompositionFactory` is the host-phase fork; the frame root gains a Vulkan arm. `VulkanBringUpHost` is reduced to the capability-probe harness over the extracted `VulkanGraphicsContext`. | offline Vulkan launch reaching the real composition with the client's own UI captured, one validation-layer run at 0 errors / 0 warnings, converging ownership ledger, App tests 4,075/3, complete Release suite 9,138/5, GL offline pixel gate 1.78e-05 |
| **V6i** ✅ | **The world arm's prerequisites**, in two parts. **V6i-1** (`df6e2a79`, reported in §5.5.12) closed §5.5.8's one-binding-two-buffers hazard with one descriptor-set pair per renderer scope, derived from the descriptor state rather than declared, and measured the ordered remainder list the world arm still needed. **V6i-2** (reported in §5.5.13) took items 1, 2 and 6 of that list in three gated commits: **1** the `TerrainClip` descriptor-set fix plus set 1's missing bindings 2 and 4, proven by `spirv-dis` and now gated by a SPIR-V-reading contract test (`f7344758`); **2** world texture CREATION crosses to `IGpuTexture``IWorldTextureArray` over `TextureAtlasManager`/`ManagedGLTextureArray`, `TerrainAtlas`'s second construction path, and `ICompositeTextureArrayBackend`'s RHI arm — with the Vulkan arm exercised at startup (`c8d0f70b`); **3** `IMeshPipelineDevice` decouples `ObjectMeshManager`/`WbMeshAdapter` from `OpenGLGraphicsDevice`. **Items 35 — the submission arms, `RetailPViewPassExecutor`, and the pass-structure merge — are the next slice's.** | pixel gate per commit (3.02e-05, 3.20e-05, 1.60e-05 vs `0ca802cd`), App tests 4,109/3 and complete Release suite 9,172/5, 3/3 desktop-witness connected run at commits 2 and 3, one validation-layer Vulkan run per commit |
| **V6i-3** | **The mesh pipeline runs on both arms, and the Vulkan frame gets a world pass**, reported in §5.5.14. Two commits: **1** `GlobalMeshBuffer` takes `GL?` and publishes `VertexStore`/`IndexStore`, `ObjectMeshManager`'s `RequireGl` narrows to the unreachable legacy upload, `VulkanMeshPipelineDevice` is `IMeshPipelineDevice`'s second implementation, and `NullWbMeshAdapter` is deleted (`fe8abacf`); **2** the clear merges into the world pass with `Store = Resolve` and descriptor sets bind at draw time (`887de4ae`). **The world renderers' submission arms and `RetailPViewPassExecutor` did NOT land** — §5.5.14 enumerates what they still need. | pixel gate per commit (4.44e-05, 3.73e-05 vs `579e0b7f`), App tests 4,112/3, one 3/3 desktop-witness connected run at HEAD, one validation-layer Vulkan run per commit, and a bit-identical (0/921,600) Vulkan capture across the pass merge |
| **V6j** ✅ | **The world arm, whole**, reported in §5.5.15. Two commits: **1** `VulkanViewportMapping` stops inverting the front face — the world arm is the mapping's first culling consumer and measured that the inversion culls terrain outright and turns every closed shell inside-out (`81fe5e1b`); **2** the three renderers' RHI submission arms as a SECOND arm per §5.5.6, both pass executors made backend-neutral behind `IWorldPassSurface`, `VulkanWorldPassScope` publishing the frame's one pass, `WorldFrameSections` carrying the three frame-global sections, and the composition that reaches them (`f84eef32`). **`ACDREAM_RENDER_BACKEND=vulkan` renders Dereth**: terrain, blending, roads, water, statics, scenery and the retained UI. Sky stays fog until V4f. | GL pixel gate 5.50e-05 vs `847f14ae` with a characterised 1231 px noise distribution, App tests 4,112/3, complete Release suite 9,175/5, GL connected `-Runs 3` at 3/3 on both columns, one validation-layer Vulkan run at 0 errors / 0 warnings, and the world PNG inspected in §5.5.15 |
| **V7** | GL-versus-Vulkan differential: `tools/run-backend-differential-gate.ps1`, 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.** | every differential checkpoint passes; both connected routes green on VK |
| **V8** | Perf gate on the RX 9070 XT, uncapped, both backends, same route. | §2 acceptance table; parity is the floor |
| **V9** | Linux + CI: X11/Wayland surfaces; 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). Physical Linux GPU row deferred post-cutover, as for Slice L. | CI green including the new job |
@ -1857,6 +1858,134 @@ submission arms and `RetailPViewPassExecutor`, which §5.5.12 item 3 established
is on the critical path because the offline gate's scene has buildings and
therefore takes the PView route.
#### 5.5.15 V6j (2026-07-28): the world arm lands, and Dereth draws on Vulkan
§5.5.14's remainder — the three renderers' submission arms,
`RetailPViewPassExecutor`, and the frame-global sections — is done.
**`ACDREAM_RENDER_BACKEND=vulkan` renders the offline scene: terrain with blended
textures and road overlays, the water edge, static world meshes, procedural
scenery, and the complete retained UI**, from the same camera as the GL capture
(`artifacts/v6j-vk2` against `artifacts/v6j-head2`). Sky is still the atmosphere
fog clear, because `SkyRenderer` is raw GL until V4f.
Three predecessors stopped at this unit under §7.1 rule 3. It landed as one slice
in two commits, because the rule is satisfied at SLICE granularity: the first
commit's piece is unreachable on its own and the second wires it and exercises it.
**1. The winding inversion was wrong, and nothing had ever asked** (`81fe5e1b`).
`VulkanViewportMapping` has inverted the front face since V6c, on the standard
negative-viewport argument. Every Vulkan consumer through V6i declares
`Cull = GpuCullMode.None`, so the mapping had never decided a fragment. The world
arm is its first culling consumer and falsified it twice on one frame: terrain —
the one single-sided surface acdream draws, `FrontFace(Ccw)` + `Cull(Back)` per
`ACRender::landPolysDraw`'s eye-side predicate — vanished entirely while issuing
190 multi-draw commands against 625 loaded landblocks, and every closed building
shell rendered inside-out with its front wall culled and its interior beams
visible through the gap. The identity mapping restores both at once. Two
independent surfaces, one change. The viewport flip itself is untouched and still
correct; what goes is the claim that a winding inversion travels with it.
**Worth generalising**: this is the third defect this campaign has found in a path
that compiled, validated clean, and had a test — because the test asserted the
behaviour rather than the requirement, and no consumer exercised it. §5.5.12's
descriptor layouts and §5.5.13's `TerrainClip` set were the first two.
**2. The world arm** (`f84eef32`). V4c's and V4d-2's content returns as a SECOND
arm, per §5.5.6's option (B): the GL arm issues the same statements in the same
order, and the encoder arm lives in three `.Rhi.cs` partials entered by one branch
per submission site. Three differences from V4c, each because the tree moved —
no binding-9 table (V4t put the slot on the device; Vulkan binds set 2), pipelines
carry the device's sample count, and **no renderer opens a pass**.
`VulkanWorldScenePhase` opens the frame's one backbuffer pass, publishes the
encoder on `VulkanWorldPassScope` for exactly the span of the inner
`WorldSceneRenderer`, and every renderer borrows it — §5.5.14 item 1's shape,
built as specified. The three frame-global sections are PUBLISHED on
`WorldFrameSections` and each renderer binds them inside the pass after its own
binds (item 2). The interior depth clear is `vkCmdClearAttachments` through the
scope (item 3); clip distances are no-ops (item 4); the collision-wireframe
`DebugLineRenderer` is composed null (item 7).
**Both pass executors became backend-neutral rather than gaining twins.** That is
the one place this slice diverged from §5.5.14's sketch, and it is why the slice
was tractable. Everything those two classes do is delegation to a renderer except
four concerns — the clip-frame publication, the doorway scissor,
`gl_ClipDistance` enablement and the interior depth clear — so those moved behind
`IWorldPassSurface` and retail's ordering, which is what the classes are actually
for, is written once for both backends. `RetailPViewPassExecutor` was on the
critical path precisely because the gate's scene has buildings (§5.5.12 item 3);
it now serves both arms unchanged.
**The A8 question is answered.** §5.5.13 and §5.5.14 asked what happens to the
Phase A8 `CullMode.Landblock → None` override the moment world materials draw on
a second backend. It carried over verbatim and is still load-bearing:
`EnvCellRenderer`'s Vulkan arm applies the same rewrite, for the same reason, and
the offline scene has no interior to disprove it with. The override is therefore
neither vindicated nor retired by this slice — it is now a divergence with two
consumers instead of one, and the dungeon pass in §5.1's checklist is what will
settle it.
**Gates.** Release build green. App tests **4,112 / 3 skips** — the unchanged
baseline; complete Release suite **9,175 / 5**. Strict GL offline pixel gate
against `847f14ae`: **5.50e-05** (31 differing pixels of 563,200), inside the
documented 931 band and 18x under the threshold. Characterised rather than
accepted, because 31 is the band's top:
```
cross-commit (847f14ae vs V6j): 21, 29, 31
same-commit (V6j vs V6j): 12, 20
maximumChannelDelta, all pairs: 46-52
```
The max delta is 4652 in every comparison INCLUDING the pure same-commit
controls, so the few large-delta pixels are a property of the capture. A
cross-commit pair at 21 against a same-commit pair at 20 is not what a systematic
shift looks like — a real one floors every cross-commit comparison above every
same-commit one. GL connected `tools/run-repeat-connected-gate.ps1 -Runs 3`:
**3/3 RENDERED** on the desktop witness and 3/3 on the client capture. One offline
Vulkan run with `VK_LAYER_KHRONOS_validation` **proven inserted by the loader**:
**zero validation errors, zero warnings**, a captured world frame, graceful close.
`tools/run-offline-vulkan-capture.ps1` is the new script that produces it.
**The Dereth PNG, inspected honestly.** Right: terrain geometry, terrain blending
and its T-code transitions, road overlays, the water edge and its shoreline,
static world meshes with correct silhouettes and closed shells, procedural
scenery, per-instance lighting, the fog colour, and the complete retained UI
including the radar. The frame matches the GL capture's framing and content
element for element below the horizon.
Wrong, and each already owned by a later slice: **the sky is flat fog** (V4f —
`SkyRenderer` is raw GL, so there is no dome, no sun, no cloud sheet, no horizon
band); **no particles** (V4e); **no paperdoll or appraisal viewport and no portal
depth mask** (V4g); **no GPU frame timings** (`NullRenderFrameGpuMeasurement`
until V4h). None of these is a defect in this slice — they are the renderers it
deliberately does not compose.
**The V7 defect list — what a differential must not be run against yet.**
1. **Sky, particles, viewports, portal mask** — absent, not wrong. V4e/V4f/V4g.
2. **§5.4's `Target: null` divergence is still live on GL.** V7 must not run until
V4g/V4h remove it, or it surfaces as an entire viewport on the wrong surface —
unchanged from §5.4's own warning, and now more binding because both backends
draw a world.
3. **`EnvCellRenderer`'s Vulkan arm is unproven by anything automated.** The
offline scene has no interior. This is V4c's exact debt, restated on the new
backend.
4. **The deferred-alpha path and the doorway scissor are unexercised** in the
offline scene: no alpha queue collects and no doorway slice is assembled, so
`DrawPreparedAlphaBatch`'s Vulkan arm and `RhiWorldPassSurface.BeginScissor`
have run zero times.
5. **MSAA must be forced off for the differential**, as §5.1 already states —
sample positions are not specified across implementations, and the world
pipelines now genuinely differ in sample count from the UI's.
6. **Bindings 4, 6, 7 and 8 cost a descriptor write per draw** (§5.5.14 item 5).
Correct and bounded, and a V8 perf item rather than a correctness one, but the
arena's `ScopeCount` is the number to watch.
**What this slice does NOT claim.** It has not run connected on Vulkan, it has not
been seen by the user, and no dungeon, portal, paperdoll or spell effect has been
drawn on it. The milestone "full game frame on Vulkan" needs V4eV4g behind it.
### 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