docs(render): record V6m - portal space crosses, and V7 gets its instrument

Adds section 5.5.18, a V6m row to the slice table and to section 5.1's
user-gate-debt table, and corrects section 5.1's "no connected route visits a
dungeon" claim - which was wrong in two directions.
connected-world-lifecycle.route.txt has carried a Facility Hub stop all along,
so the lifecycle gate did reach an interior even though nothing compared its
pixels; and reaching one turns out not to be the same as being able to compare
it.

The V7 list moves in three ways. Item 1 closes: every production renderer draws
on both arms. The appraisal-viewport half-discharge that V6k opened and V6l
carried closes too - the view was driven on both backends and inspected. And a
new item 7 opens, which is the honest result of firing the instrument once: with
the sky band and the animated portal masked, the smoke pair still differs in
1.17% of the frame, about 12x the pinned threshold, while the chat panel on its
own differs in 42 pixels of 106,560 - inside the threshold. So the 2-D retained
UI is already at parity and the residual lives in the 3-D pass, at silhouette
edges. That is a better starting position for V7 than a single aggregate number
would have been, and it is why the smoke was attributed rather than just
reported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-28 18:43:16 +02:00
parent a99f517ec2
commit 3b62b9bfa3

View file

@ -515,6 +515,7 @@ session, rather than one at a time:
| V6e | **Particles, again — and now sky.** The particle half is V2c's and V4e's debt restated: the offline scene draws no particles, so nothing automated saw the varying retype or the ACDREAM_TEXTURE_NONE sentinel. The sky half is new and larger: V6e moved a dozen loose uniforms into a `SkyParams` uniform buffer and moved the sky's texture from a bound unit-0 texture-plus-sampler to a bindless (texture, wrap) table slot, and the gate masks the sky band for determinism. What WAS checked, and should be read as bounding the risk rather than closing it: a base-versus-head offline capture at **all seven day groups**, matching in gradient, cloud sheet, horizon band and fog on every one — including day group 2's salmon cloud band and day group 6's green band, which exercise texture sampling, tint, blend and fog together — plus 3/3 RENDERED on the desktop-witness repeat gate. **What remains unproven is pixel-exactness and the parts of the dome the fixed outdoor camera cannot see**: the sun and moon (additive surfaces high in the sky) and the rain cylinder, which is the one sky mesh that surrounds the camera and the one whose REPEAT wrap mode is most visible. Stand outside at dawn or dusk, and stand in rain |
| V6k | **The sky band and the paperdoll — both CHECKED rather than banked, and recorded here so the next slice does not re-open them.** Commit 1 carries a seven-day-group before-and-after comparison on GL (V6e's method) because the gate masks the sky; commit 2 carries a connected run that presses `ToggleInventoryPanel` and captures the doll through the new render target (`artifacts/v6k-paperdoll`). **What remains uncovered:** the creature-appraisal viewport, which is the same class and the same code as the paperdoll but was not itself driven; and the sun, moon and rain cylinder, which V6e already filed and a fixed outdoor camera at one time of day still cannot see |
| V6l | **The Vulkan paperdoll and a particle effect — both CHECKED rather than banked, and both connected, because the offline gate reaches neither.** The doll capture (`artifacts/v6l-vk-paperdoll3` against `artifacts/v6l-gl-paperdoll`) is also the no-regression check for making the viewport's V orientation backend-derived. The particle capture (`artifacts/v6l-vk-poi` against `artifacts/v6l-gl-poi`) is Holtburg's forge plume and glint field, cropped 4× at `artifacts/crop-vk-glow.png` / `crop-gl-glow.png`. **What remains uncovered:** the creature-appraisal viewport, which is the same class and the same code as the paperdoll but was again not itself driven — V6k's half-discharge, carried forward unchanged; and the portal depth mask, which drew no pixel in either capture because neither run entered a building aperture. Check it with the dungeon/doorway pass above |
| V6m | **The portal tunnel, the creature-appraisal viewport and an interior EnvCell — all three CHECKED, connected, on BOTH backends, because the offline gate reaches none of them.** `artifacts/v6m-gl-tunnel` versus `artifacts/v6m-vk-tunnel`: ten transit frames, the examination window over a Brown Rabbit, and the Facility Hub's interior. **This discharges V6k's and V6l's carried appraisal-viewport half-discharge** — the view that shares the paperdoll's class and code has now been driven itself. **What remains uncovered:** the portal DEPTH MASK still drew no pixel, because none of these runs stood in a building aperture either; and the interior pair is eyes-on only, because the indoor spring-arm camera settled to different distances in the two runs (31.2% differing, a route defect rather than a renderer one — see §5.5.18) |
| V6f | **Terrain seen through a doorway clip region.** The offline gate covers terrain heavily — blending, road overlays and the water edge are most of the frame, and every one of those samples goes through `terrainTiling()`, so the std140 stride and the new `ACDREAM_SAMPLE_ARRAY` reads are well proven. What it cannot see is the one terrain path with its own binding: the clip UBO at binding 2, exercised when terrain is viewed through a doorway. Binding 3 now sits beside it and is rebound per draw, so a bind-order mistake would show exactly there. Check it with the dungeon/doorway pass above |
**The user confirmed on 2026-07-27 that the local ACE server is always available
@ -533,10 +534,17 @@ The checklist, in the order that exercises the most per minute:
| Cast a spell | Particles, and the inverse-alpha blend added at V4c |
| Stand outside at dawn or dusk | Sky, which the offline gate masks for determinism |
Worth noting: **no existing connected route visits a dungeon.** Every stop in
`connected-r6-soak.route.txt` and `connected-world-lifecycle.route.txt` is outdoor,
which is why EnvCell coverage was missing from the automated gates too, not just the
offline one. Adding an interior stop to those routes is the durable fix.
Worth noting: **no connected route used to visit an interior, and one does now.**
Every stop in `connected-r6-soak.route.txt` is outdoor, which is why EnvCell
coverage was missing from the automated gates too and not just the offline one.
V6m added the interior stop to the new
`connected-backend-differential.route.txt` — the Facility Hub's cell `0x164`,
which is `>= 0x100` and therefore indoor. Two corrections to the earlier claim,
both worth keeping straight: `connected-world-lifecycle.route.txt` has in fact
carried a Facility Hub stop all along, so the lifecycle gate did *reach* an
interior even though nothing compared its pixels; and reaching one is not the
same as being able to compare it, because the indoor spring-arm camera settles
to a slightly different distance per run (§5.5.18).
MSAA is left at the quality preset for GL-versus-GL self-differentials, where it
is deterministic. The V7 GL-versus-Vulkan differential must force MSAA off,
@ -581,7 +589,8 @@ tenth pair with no consumer at all; see the V6e report.
| **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 |
| **V6k** ✅ | **Sky, viewports, and §5.4**, reported in §5.5.16. Two commits: **1** `SkyRenderer`'s RHI arm — V4f's content, two blend pipelines, `SkyParams` as a per-draw ring slice, the first Vulkan consumer of set 1 binding 4 — plus the retirement of the last interim `GlBindlessHandleTable` (`22aa2edc`); **2** `PrivateEntityViewportRenderer` onto `IGpuRenderTarget`, the deletion of §7.1's external-texture seam, and **§5.4's obligation discharged** (`eb7e6b4e`). **Particles did not land**: the pinned contract cannot express instanced vertex input, which is what both particle pipelines are built on. | GL pixel gate 4.43e-05 then 4.08e-05 (25 and 23 px, band 931), App tests 4,109/3, complete Release suite 9,172/5, GL connected `-Runs 3` at 3/3 on both columns per commit, one validation-layer Vulkan run at 0 errors / 0 warnings per commit, a seven-day-group GL sky comparison, a connected paperdoll capture, and a GL-versus-Vulkan inspection in §5.5.16 |
| **V6l** ✅ | **Particles, the portal mask and the viewports**, reported in §5.5.17. Three commits, one per contract amendment: **1** instanced vertex input (`GpuVertexLayout` per-binding stride and input rate, `BindVertexBuffer(binding, …)`, `GpuVertexFormat.UInt1`) plus `ParticleRenderer`'s RHI arm, the standalone particle texture cache on both arms, and the stride-equals-the-uploaded-record gate for every RHI vertex layout (`b1ad1d48`); **2** the stencil dimension (`StencilTest` + `GpuStencilState` + `SetStencil`), `portal_depth` as a committed shader pair, `PortalDepthMaskRenderer`'s RHI arm, and the ambient stencil/colour-mask restore (`eced67d0`); **3** a layered sampled view per Vulkan render target, sample-count pipeline variants for `WbDrawDispatcher`, the composite texture cache on both arms, and the backend-derived viewport V orientation (`2e8b8b91`). | GL pixel gate per commit (3.20e-05, 2.31e-05, 3.55e-05; band 931 px), App tests 4,121/4,129/4,129 and complete Release suite 9,184/9,192/9,192, GL connected `-Runs 3` at 3/3 on both columns per commit, one validation-layer Vulkan run at 0 errors / 0 warnings per commit, and connected Vulkan paperdoll and particle captures inspected against GL |
| **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 |
| **V6m** ✅ | **Portal space, and V7's instrument**, reported in §5.5.18. Two commits: **1** `PortalTunnelPresentation`'s RHI arm — the last raw-GL world-adjacent renderer — as a backbuffer pass published on `IWorldPassScope`, clearing to retail's opaque portal-space black rather than loading it, plus the deletion of `NullLocalPlayerTeleportPresentation` (`59c6b2ae`); **2** `tools/run-backend-differential-gate.ps1` and `connected-backend-differential.route.txt`, with MSAA forced off on both launches, the repeat gate's desktop-witness guards, and **an interior EnvCell stop** — §5.1's durable fix for the campaign's oldest coverage gap (`a99f517e`). One smoke pair was run and is reported in full. | GL pixel gate 4.97e-05 (28 px) vs `280f3b3f` against a 3.55e-05 (20 px) same-commit control, band 931; App tests 4,132/3 and complete Release suite 9,195/5; GL connected `-Runs 3` at 3/3 on both columns; one validation-layer Vulkan run at 0 errors / 0 warnings; connected portal-tunnel, creature-appraisal and interior-EnvCell captures on BOTH backends, inspected in §5.5.18 |
| **V7** | 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, 1.17% with the sky band and the animated portal masked, with the 2-D retained UI already inside the threshold on its own. | 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 |
| **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** |
@ -2343,6 +2352,160 @@ visibility and deferred-alpha paths remain unexercised by anything automated.
itself driven — the same half-discharge §5.1's V6k row records, carried
forward.
#### 5.5.18 V6m (2026-07-28): portal space crosses, and the differential gets its instrument
The last raw-GL world-adjacent renderer is on both arms, and V7's instrument
exists and has been fired once.
**1. Portal space** (`59c6b2ae`). `PortalTunnelPresentation` draws on both arms.
Nothing about the scene moved — the same synthetic Setup resolved through the
same client-enum mapping, the same 40 fps `CSequence`, the same retail rotation
cadence and distant light, through the same already-dual-arm
`WbDrawDispatcher`. What forked is where the draw is recorded: GL keeps its
`GLStateScope` and its depth-only `glClear`; the RHI arm opens a backbuffer pass
of its own and publishes it on `IWorldPassScope` for the span of the draw, which
is V6l's viewport shape and required for V6l's reason — the dispatcher's RHI arm
borrows its pass rather than opening one. Publication sits between `BeginPass`
and `UploadRetailLight`, because publishing resets the frame-global sections and
this scene wants its own light rather than the world's.
**The one substantive decision is that the pass CLEARS colour rather than
loading it, and it is exact rather than approximate.** Retail preserves the
colour target and clears only depth (`UIViewportObject::DrawContent 0x006950A5`
`Clear(4)` = `D3DCLEAR_ZBUFFER`), and so does the GL arm. A Vulkan pass cannot
inherit an image the way a bound framebuffer can: under MSAA the frame's world
pass RESOLVES into the swapchain image and stores `DontCare` into the
multisampled scratch, so a second multisampled pass declaring `Load` would load
undefined contents — §5.5.12 item 5, the same hazard that merged the clear into
the world pass. What makes re-clearing lossless is an invariant the frame graph
already enforces: `RenderFrameFoundation.PortalViewportVisible` and the scene's
own `IsVisible` are the same value read once at the top of the frame, and
`WorldSceneRenderer` returns without drawing when it is set — so whenever portal
space draws, the backbuffer holds exactly the opaque black
`SceneTool::BeginScene 0x0043DAD0` establishes and nothing else. The alternative,
a single-sampled `Load` pass over the resolved image, would have been both a
silent MSAA divergence and invalid, because the backbuffer's depth attachment is
multisampled.
`NullLocalPlayerTeleportPresentation` is deleted with the backend condition that
produced it, and `SessionPlayerComposition`'s transfer is unconditional again.
**2. The differential instrument** (`a99f517e`).
`tools/run-backend-differential-gate.ps1` runs one connected route twice —
`ACDREAM_RENDER_BACKEND=gl` then `vulkan` — pairs the PNGs by name, compares
each at tolerance 2 / fraction 0.001, and writes one verdict table.
`ACDREAM_MSAA_SAMPLES=0` is forced on **both** launches, `ACDREAM_DAY_GROUP` is
pinned on both, and the desktop witness plus its three guards (blank frame,
overlapping window or locked screen, stray input) are lifted from the repeat-run
gate rather than reinvented. `connected-backend-differential.route.txt` is
written for two-launch determinism — identity quaternions on every teleloc, the
client-only time-of-day override, no movement between arrival and capture — and
**it carries an interior EnvCell stop**, which is the durable fix §5.1 names for
the campaign's oldest coverage gap.
**Two files, ASCII with CRLF, and that is not cosmetic.** PowerShell 5.1 reads a
BOM-less `.ps1` as ANSI, so a UTF-8 em dash inside a double-quoted string is a
parse error. The first version of the script had one and did not run.
**3. The smoke pair, honestly.** ONE pair, on the route's first stop, MSAA off,
GL versus Vulkan at Holtburg: **170,697 of 921,600 pixels differ — 18.52%**,
maximum channel delta 255. Attributed with a difference map
(`artifacts/v6m-diff-smoke.png`):
- **89% of it is in the top 240 rows.** Applying the offline gate's top-280 sky
mask takes the pair to **11,480 of 563,200, 2.04%**. That band is the
scrolling cloud sheet — which advances with WALL time, not with the Dereth
clock the route pins — plus the treeline behind it.
- **Masking the animated portal beside the character too** takes it to **6,190
of 529,450, 1.17%**, mean channel delta 0.49. A portal's scrolling texture is
phase, exactly like an emitter's age.
- **The 2-D retained UI is not implicated, and this is the most useful number
the smoke produced.** Compared over the chat panel alone — static content,
same seven lines on both runs — the two backends differ in **42 pixels of
106,560, fraction 3.94e-04, maximum channel delta 3**. That is *inside* the
0.001 threshold on its own, so `TextRenderer`, the sprite path and the glyph
path are already at parity and a global gamma or half-pixel offset is
excluded. **The residual is in the 3-D pass.**
- What visibly remains is thin outlines on silhouette edges throughout the
frame, including static buildings, plus the vitals readouts, whose stamina and
mana genuinely advanced between two logins minutes apart.
So V7's distance to 0.001 is roughly **12x** with the two known phase
populations removed, and the population to explain is edges in the world pass.
`-MaskTopPixels` exists on the script for that conversation and **defaults to
0**: the gate is a strict identity check on the whole frame unless someone
deliberately asks otherwise (§7.1 rule 2).
**Gates, per commit.** Release build green. App tests **4,132 / 3 skips** against
the 4,129 baseline (three new: the retail black constant, the RHI arm's
composition precondition, and the both-arms composition assertion); complete
Release suite **9,195 / 5**, with one `AcDream.Content` failure in the
solution-wide run that passes 124/124 rerun alone — the documented rerun-singly
flake class, not carried forward as a claim. Strict GL offline pixel gate
against `280f3b3f`: **4.97e-05 (28 px of 563,200)**, inside the documented 931
band, with a same-commit control pair taken immediately afterwards at **3.55e-05
(20 px)**. GL connected `-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**. Commit 2 changes no product code, so those gates stand
unchanged for it; the smoke run is its own evidence, having driven a complete
connected Vulkan session end to end with a graceful exit.
**The three captures the offline gate cannot reach, connected and inspected.**
- **Portal space on both backends** (`artifacts/v6m-gl-tunnel` versus
`artifacts/v6m-vk-tunnel`, ten frames each at 400 ms across the transit). The
retail wormhole draws on Vulkan: the same tube, the same blue-grey shading and
drifting purple motes, the retained UI composited above it, and — the thing
the clear decision above is about — an opaque black surround with no world
bleeding through. The two runs are at different rotation and animation phases,
because the rotation cadence is drawn from `Random` and two launches cannot
agree on it, so this is an eyes-on comparison rather than a numeric one.
- **The creature-appraisal viewport on both backends**
(`artifacts/v6m-gl-tunnel/screenshots/appraisal.png` versus the Vulkan
counterpart). **This discharges V6k's and V6l's carried half-discharge**: the
appraisal view shares the paperdoll's class and code and had never itself been
driven. Examining a Brown Rabbit at Caul opens the examination window with the
same title, the same "Rabbit / Character Level 4" header, the same nine
attribute rows, and the creature rendered through the private viewport at the
same scale and the right way up on both arms. A numeric pair over the window
is confounded — the retail panel is semi-transparent over a swaying treeline,
and the rabbit has its own animation cursor — so this too is eyes-on, as V6l's
paperdoll was.
- **An interior EnvCell on both backends** (`interior.png` in the same two
directories): the Facility Hub's brick walls, doorway and magenta per-cell
ambient render the same on Vulkan. **A numeric pair is NOT yet available and
the reason is a route defect, not a renderer one**: the frames differ in
31.2%, and the cause is visible on inspection — the indoor spring-arm camera
had settled to slightly different distances in the two runs, which moves every
near-field edge. The interior stop needs a longer settle, or a framing that
does not depend on the camera collapsing, before V7 can use it as a
checkpoint.
**The V7 defect list, updated.**
1. ~~`PortalTunnelPresentation` has no Vulkan arm~~ — landed. **Every production
renderer now draws on both arms.**
2. **`EnvCellRenderer`'s Vulkan arm now has a connected route stop and an
eyes-on pair, but no numeric one.** Narrowed again from §5.5.17 item 2. The
route file carries the interior stop; making it comparable needs the camera
settle fixed above. The deferred-alpha path and the doorway scissor remain
unexercised by anything automated.
3. **MSAA is forced off by the instrument** (was: must be). What the smoke
measured in its place is the sky band — the cloud sheet runs on wall time and
no route flag pins it, so V7 must decide between masking it, pinning the
phase, or accepting it. 89% of the smoke's difference lived there.
4. **Bindings 4, 6, 7 and 8 cost a descriptor write per draw** (unchanged; a V8
item).
5. **The portal depth mask's two arms do not share a shader source** (unchanged;
guarded by `PortalDepthShaderParityTests`, resolved by deletion at V11).
6. ~~The appraisal viewport was never itself driven~~ — driven on both backends
and inspected, above.
7. **NEW: the world pass differs at silhouette edges.** With the sky band and
the animated portal masked the smoke still reports 1.17%, about 12x the
threshold, and the chat-panel measurement rules the 2-D path out. This is
V7's first lead and it is a real one.
### 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