From d5deeb3314a5f5587c27c11d295a9d775c666013 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 27 May 2026 20:24:59 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20=E2=80=94=20remove?= =?UTF-8?q?=20cull-restore=20at=20EnvCell=20exit=20(lets=20IndoorPass=20in?= =?UTF-8?q?herit=20cull-off)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual-gate-#4 evidence revealed the prior commit's cull-restore-at-exit addition was wrong. The Landblock→None CullMode override worked correctly for cell-mesh polys, but the cull-back state I restored at Render exit propagated to the subsequent `dispatcher.Draw(IndoorPass)` call. The dispatcher's IndoorPass renders AC's cottage shell — landblock-baked GfxObj parts (wooden floor planks, wall slabs) whose pos-side winding + our FrontFace=CCW + cull-back = floor poly is back-facing and culled. User saw light blue sky through the floor in gate-#4. Reverting the cull-restore lets cull-disabled propagate from EnvCellRenderer.Render through IndoorPass. Cottage shell renders double-sided so the floor + wall slabs are visible from any angle. Step 4's gl.Enable(EnableCap.CullFace) at the terrain pass (line ~10768) + the cleanup block's enable (line ~10870) re-establish cull-back BEFORE the LiveDynamic dispatcher.Draw fires — so chars, NPCs, doors still render solid (no see-through-head regression from gate-#3's ACDREAM_A8_DISABLE_CULL=1 diagnostic). The retail-faithful long-term fix is matching WB's `glFrontFace(GLEnum.CW)` globally (per GameScene.cs:843) so cull-back selects the correct side for AC's natural polygon winding without needing double-sided rendering. That requires a wider audit of every consumer's FrontFace assumption (translucent crystal renderer + others) and is deferred. 14/14 EnvCellRenderer tests pass. Build green. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Rendering/Wb/EnvCellRenderer.cs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs index ad3e4e1..3bf2071 100644 --- a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs +++ b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs @@ -815,21 +815,32 @@ 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; + // Phase A8 (2026-05-28 visual-gate-#4 follow-up): NO cull-restore + // at exit. The Landblock→None override can leave cull DISABLED + // if the last batch was Landblock — and that's intentional: the + // subsequent `dispatcher.Draw(IndoorPass)` call in + // RenderInsideOutAcdream's Step 3 wants cull-off too, because + // AC's cottage-shell GfxObj parts (the wooden floor planks + + // wall slabs that the player walks on / through) have winding + // that gets back-face-culled by the dispatcher's default + // FrontFace=CCW. Letting cull stay off through IndoorPass + // renders both shell and cell mesh double-sided, so floors are + // visible from above (and inverted-front-facing wall slabs are + // visible from inside the room). Step 4's + // `gl.Enable(EnableCap.CullFace)` (line ~10768) + the cleanup + // block's enable (line ~10870) re-establish cull-back before + // LiveDynamic chars / NPCs / doors render — so those still + // look solid (no see-through head). The static `_currentVao` + // is reset because the next Render call's batch loop needs to + // re-issue BindVertexArray regardless; `_currentCullMode` is + // intentionally left at None so the cache matches actual GL + // state until the next Render call's per-batch SetCullMode + // either confirms or re-sets it. + // + // The retail-faithful long-term move is matching WB's + // glFrontFace(CW) globally (GameScene.cs:843) so cull-back + // selects the correct side for AC's polygon winding without + // double-sided rendering — deferred until a wider audit. // Update frame stats for probe emission at the call site. _lastFrameStats.CellsRendered = filter?.Count ?? snapshot.BatchedByCell.Count;