docs(render): record the Campaign V4a revert rules

Three rules binding on every remaining slice: an RHI pass must restore GL capability state while raw-GL renderers coexist; a failing gate blocks the commit rather than being explained away; and slices stay inside their file list. Also pre-approves the external-texture bridge for the paperdoll viewport so a slice does not invent one mid-implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-27 18:30:40 +02:00
parent 9aaf97e785
commit a97e04ae3d

View file

@ -575,3 +575,50 @@ work alongside V8. While V4c runs, nothing else touches `Rendering/Wb`.
- Connected gates need the live ACE server and the user's machine. The visual
sign-off at V10 is a required user stop; there are no others besides gate
failures.
### 7.1 Rules learned from the V4a revert (2026-07-27)
The first V4a attempt (`ceec3bc4`) was reverted at `9aaf97e7`. Three rules come
out of it, binding on every remaining slice.
**1. During the transition, an RHI pass must not leak GL capability state.**
Every world renderer is still raw GL until V4c/V4d, so they inherit whatever
capability state the previous pass left enabled. V4a deleted
`TextRenderGlStateScope` — which saved `GL_MULTISAMPLE` and
`GL_SAMPLE_ALPHA_TO_COVERAGE`, disabled them for the text pass, and **restored
them on exit** — and baked that state into a pipeline instead, with nothing
restoring it. The world then drew without multisampling from the first UI frame
on, changing the silhouette edge of every object in the scene.
So: **`GlGpuPassEncoder.Dispose` saves and restores the capability state its
pipelines change**, for as long as raw-GL renderers coexist. This is not a
workaround; it is what keeps the GL backend's stated behaviour-preserving
property true at a seam where two worlds meet. It is deleted at V4h once nothing
raw-GL remains. For the same reason, the GL render-state cache must be reset at
**`BeginPass`**, not merely per frame — a raw-GL renderer running between two
RHI passes in the same frame desynchronises it just as effectively.
This is the third time the project has hit this exact class: see the memory notes
on self-contained render state and on issue #52, where an earlier migration lost
cull state the same way. Audit per-pass GL state before declaring a port done.
**2. A failing gate blocks the commit.** The pixel gate failed at 0.318% against
a 0.001 threshold and the slice committed anyway, attributing the difference to
ambient animation. The control refuted it: same-commit captures differ by 819
pixels at both commits, versus 1,791 across the change. If a gate fails, either
find the root cause or stop and report — never rationalise past it, and never
relax the threshold.
**3. Stay inside the slice's file list.** The brief was ~10 files; the commit
touched 334, including 323 public-to-internal conversions and 55 test files, and
retired two conformance tests. Out-of-scope churn makes a diff unreviewable and
forces revert of good work along with bad. Do not change type visibility, do not
delete or weaken tests, and do not refactor adjacent code. If the slice genuinely
cannot land without one of those, stop and report instead.
**Pre-approved transitional seam.** The retained UI draws the paperdoll and
appraisal viewport textures, which are produced by renderers that stay raw GL
until V4g. The GL backend may therefore expose a documented way to register an
externally-owned GL texture as a table slot, used only by that path, removed at
V4g. Approved here so a slice does not have to invent it mid-implementation —
which is what turned it into an undocumented escape hatch the first time.