fix(render): Phase A8 — cell-mesh Landblock CullMode → None + cull state restore
The cull A/B diagnostic (prior commit's ACDREAM_A8_DISABLE_CULL=1) in
visual-gate-#3 confirmed: cell-mesh polys are being culled by back-face
culling, which is why floors disappear when looking down from inside a
room. Per-cell audit data showed every cell-mesh batch has
CullMode.Landblock — assigned because AC's CellStruct polys carry
SidesType=Landblock in the dat. Our SetCullMode maps Landblock to
glCullFace(Back), matching WB.
Root cause:
WB sets `glFrontFace(GLEnum.CW)` globally at GameScene.cs:843. Our
WbDrawDispatcher.cs:1056 sets `glFrontFace(CCW)` — the GL default,
opposite of WB. With our flipped-from-natural fan triangulation in
BuildCellStructPolygonIndices (which emits (i, i-1, 0) for each fan
triangle, reversing the input vertex order), the resulting effective
winding from the camera's perspective is OPPOSITE WB's. Cull-back then
removes the OPPOSITE face from what WB does — hiding the floor side
that should be visible from inside the room.
Within a single cell-mesh batch, the polys face every direction (walls
outward, floor up, ceiling down) but all share CullMode.Landblock. No
single cull setting can be correct for all three orientations
simultaneously — the retail-faithful approach is to render cell polys
double-sided (cull off).
Two changes scoped to EnvCellRenderer.RenderModernMDIInternal so other
renderers aren't affected:
1. Remap CullMode.Landblock → None when iterating per-cull-mode
batch groups. Cell polys render with cull disabled, all faces
visible. CullMode.Landblock is only assigned by
PrepareCellStructMeshData (cell polys) in this codebase — terrain
uses a different render path. Scope is exactly right.
2. Explicitly Enable(CullFace) + CullFace(Back) at Render exit so the
dispatcher's subsequent IndoorPass + LiveDynamic Draws don't
inherit the cull-disabled state. The see-through-head symptom in
visual-gate-#3 was caused by exactly this state leak from the
ACDREAM_A8_DISABLE_CULL=1 diagnostic; the proper fix needs the
explicit restore. Also updates the static `_currentCullMode` cache
so the next Render call's first SetCullMode comparison is correct.
Removed the ACDREAM_A8_DISABLE_CULL diagnostic env var — its role as
A/B test is complete. 14/14 EnvCellRenderer tests pass. Build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b19f3c14a9
commit
0940d7961a
1 changed files with 34 additions and 14 deletions
|
|
@ -81,16 +81,6 @@ public sealed unsafe class EnvCellRenderer : IDisposable
|
|||
private static uint _currentVao;
|
||||
private static CullMode? _currentCullMode;
|
||||
|
||||
// Phase A8 A/B diagnostic (2026-05-28 visual-gate-#2 follow-up):
|
||||
// ACDREAM_A8_DISABLE_CULL=1 forces every batch's effective CullMode to
|
||||
// None (no face culling). Used to isolate whether the missing-floor
|
||||
// symptom is caused by polygon-winding+CullMode interaction or by
|
||||
// something else (lighting, depth, alpha). If the floor appears with
|
||||
// this set, cull/winding is the bug. If not, look elsewhere. Static
|
||||
// because it's read once at startup; no need to re-query per draw.
|
||||
private static readonly bool _forceCullModeNone =
|
||||
string.Equals(Environment.GetEnvironmentVariable("ACDREAM_A8_DISABLE_CULL"), "1", StringComparison.Ordinal);
|
||||
|
||||
public bool NeedsPrepare { get; private set; } = true;
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
|
|
@ -825,6 +815,22 @@ public sealed unsafe class EnvCellRenderer : IDisposable
|
|||
_gl.BindVertexArray(0);
|
||||
_currentVao = 0;
|
||||
|
||||
// Phase A8 fix (2026-05-28 visual-gate-#3 follow-up): explicitly
|
||||
// restore cull-back at exit. The Landblock→None override above
|
||||
// can leave cull DISABLED if the last batch's CullMode was
|
||||
// Landblock — which would leak into the subsequent dispatcher
|
||||
// draws (IndoorPass building shells, then LiveDynamic chars +
|
||||
// NPCs + doors), making them all render see-through (no
|
||||
// back-face cull). The see-through-head symptom in the
|
||||
// ACDREAM_A8_DISABLE_CULL=1 A/B test was caused exactly by
|
||||
// this state leak. Re-enabling cull here restores the
|
||||
// dispatcher's expected default and updates our static cache
|
||||
// so the next Render call's first SetCullMode comparison is
|
||||
// correct.
|
||||
_gl.Enable(EnableCap.CullFace);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
_currentCullMode = CullMode.CounterClockwise;
|
||||
|
||||
// Update frame stats for probe emission at the call site.
|
||||
_lastFrameStats.CellsRendered = filter?.Count ?? snapshot.BatchedByCell.Count;
|
||||
_lastFrameStats.TrianglesDrawn = 0;
|
||||
|
|
@ -997,10 +1003,24 @@ public sealed unsafe class EnvCellRenderer : IDisposable
|
|||
foreach (var group in batchesByCullMode)
|
||||
{
|
||||
var cullMode = (CullMode)(group.Key % 4);
|
||||
// A/B diagnostic: ACDREAM_A8_DISABLE_CULL=1 overrides every batch
|
||||
// to no-culling. Reveals whether floor-missing is a cull/winding
|
||||
// bug or something else.
|
||||
if (_forceCullModeNone) cullMode = CullMode.None;
|
||||
// Phase A8 fix (2026-05-28 visual-gate-#3 evidence): override
|
||||
// CullMode.Landblock to None for cell-mesh batches. WB sets
|
||||
// glFrontFace(CW) globally (GameScene.cs:843) so its CullMode
|
||||
// mapping (Landblock→Back) culls the correct side; we set
|
||||
// glFrontFace(CCW) in WbDrawDispatcher (line 1056) so the
|
||||
// mapping would cull the OPPOSITE side, hiding cell floors.
|
||||
// Cell-mesh polys with CullMode.Landblock represent the floor +
|
||||
// walls + ceiling of a single room — they face different
|
||||
// directions but share one CullMode value, so a single cull
|
||||
// setting can't be correct for all of them. The retail-faithful
|
||||
// approach is double-sided rendering for cell polys (cull off),
|
||||
// matching what the cull-disable A/B diagnostic empirically
|
||||
// confirmed (floor visible with cull off in visual-gate-#3).
|
||||
// CullMode.Landblock is only ever assigned in this codebase by
|
||||
// PrepareCellStructMeshData (cell polys) — terrain has its own
|
||||
// renderer that doesn't go through this code path — so this
|
||||
// override is scoped exactly right.
|
||||
if (cullMode == CullMode.Landblock) cullMode = CullMode.None;
|
||||
if (_currentCullMode != cullMode)
|
||||
{
|
||||
SetCullMode(cullMode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue