diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index f68e8c3..d0be1cf 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -11176,20 +11176,31 @@ public sealed class GameWindow : IDisposable set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery); _a8PerfLastStaticStats = _wbDrawDispatcher.LastDrawStats; - // Step 5: per-other-building 3-bit stencil pipeline. - // WB VisibilityManager.cs:157-232 + // Step 5: per-other-building 3-bit stencil pipeline (cross-building + // visibility — wire-in #3). WB VisibilityManager.cs:157-232. // - // GATED OFF BY DEFAULT. Current acdream does not yet have WB's - // portal-manager visibility/occlusion lifecycle; feeding this loop - // directly from all loaded building registries causes unrelated - // buildings' EnvCells to overwrite exterior walls through stale or - // over-broad portal masks. Keep the apparatus, but require an explicit - // opt-in until the portal list is proven equivalent to WB's - // _visibleBuildingPortals. - bool step5Enabled = string.Equals( - Environment.GetEnvironmentVariable("ACDREAM_A8_STEP5"), "1", - StringComparison.Ordinal); - if (step5Enabled && didInsideStencil && otherBuildings.Count > 0) + // Phase A8.F (Task 8, 2026-05-28): UNGATED. Previously gated behind + // ACDREAM_A8_STEP5 because bit 1 was the old flat all-exit-portals + // mask — feeding the cross-building pass from that over-broad mask let + // unrelated buildings' EnvCells overwrite exterior walls. That risk is + // gone now: bit 1 is the recursively-CLIPPED OutsideView written by + // MarkAndPunchNdc above (Task 6). Step 5a marks bit 2 only WHERE bit 1 + // is set (StencilFunc Equal,3,0x01), so building B's cells render only + // where B's portals overlap our correctly-clipped opening (stencil==3 = + // bit1 AND bit2). In a cellar the stairwell-clipped OutsideView is tiny, + // so stencil==3 is essentially empty and Step 5 does ~nothing → no + // cellar artifacts. Non-overlapping buildings likewise produce empty + // stencil==3, so the frustum/radius-culled `otherBuildings` list is safe + // either way. + // + // The builder's CrossBuildingViews field is intentionally UNUSED: + // cross-building visibility is exit-portal screen-overlap, handled + // entirely by this loop reading the clipped bit 1. (CrossBuildingViews + // is dead output, slated for cleanup in a later task.) + // + // Runs whenever we're inside with a non-empty clipped OutsideView + // (didInsideStencil) and there is at least one other visible building. + if (didInsideStencil && otherBuildings.Count > 0) { gl.Enable(EnableCap.StencilTest); gl.ColorMask(false, false, false, false); @@ -11262,9 +11273,10 @@ public sealed class GameWindow : IDisposable // anything that relies on alpha-to-coverage writing alpha). Restore // full ColorMask before returning to the outer render frame. // - // Step 5's iteration loop (when ACDREAM_A8_STEP5=1) leaves - // DepthMask=false / CullFace=disabled / ColorMask=(f,f,f,f) on - // its last iteration. Restore to acdream-default before returning. + // Step 5's iteration loop (now always-on when inside + other + // buildings overlap) leaves DepthMask=false / CullFace=disabled / + // ColorMask=(f,f,f,f) on its last iteration. Restore to + // acdream-default before returning. gl.ColorMask(true, true, true, true); gl.DepthMask(true); gl.DepthFunc(DepthFunction.Less); @@ -11297,7 +11309,8 @@ public sealed class GameWindow : IDisposable // - [envcells] cells>=1 tris>=1 filterCnt>=1 for at least one indoor frame // - [stencil] op=mark verts>0 fires per camera-building // - [draworder] shows steps 1 → 2 → 3 → 4 per indoor frame - // (and 5{a,b,c,d} only when ACDREAM_A8_STEP5=1) + // (and 5{a,b,c,d} whenever inside + a visible other building's + // portals overlap the clipped bit-1 OutsideView) private int _phaseA8DrawOrderFrame = 0;