feat(render): Phase A8.F — wire-in #3 cross-building via clipped bit-1 (ungate Step 5)

This commit is contained in:
Erik 2026-05-29 13:00:22 +02:00
parent 5a012c05f0
commit e0051e0764

View file

@ -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;