diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 5ed1677c..1aa4f592 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7656,9 +7656,12 @@ public sealed class GameWindow : IDisposable _gl.DepthMask(true); // depth clears honor glDepthMask (c4df241 lesson) _gl.Clear(ClearBufferMask.DepthBufferBit); }, - DrawExitPortalMasks = clipRoot.IsOutdoorNode - ? null - : sliceCtx => DrawRetailPViewExitPortalSeal(sliceCtx, envCellViewProj), + // BR-2: interior roots SEAL exit doors at true depth (#108); + // outdoor roots PUNCH building entry apertures to far-Z so + // flooded interiors show through doorways from outside. + DrawExitPortalMasks = sliceCtx => + DrawRetailPViewPortalDepthWrite(sliceCtx, envCellViewProj, + forceFarZ: clipRoot.IsOutdoorNode), DrawCellParticles = sliceCtx => DrawRetailPViewCellParticles(sliceCtx, camera, camPos), EmitDiagnostics = result => @@ -7804,6 +7807,11 @@ public sealed class GameWindow : IDisposable MaxSeedDistance = 48f, LandblockEntries = _worldState.LandblockEntries, SetTerrainClipUbo = uboId => _terrain?.SetClipUbo(uboId), + // BR-2: outside-looking-in — PUNCH building entry apertures + // to far-Z so the flooded interior shows through the doorway. + DrawExitPortalMasks = sliceCtx => + DrawRetailPViewPortalDepthWrite(sliceCtx, envCellViewProj, + forceFarZ: true), }); if (portalResult is not null) @@ -9559,18 +9567,29 @@ public sealed class GameWindow : IDisposable DisableClipDistances(); } - // BR-2 seal: re-stamp the TRUE depth of every outside-leading portal of this - // cell, clipped to the slice's view region — retail PView::DrawCells loop 1 - // (Ghidra 0x005a4840, pc:432783-432786): after the landscape draws through - // the outside views and the depth buffer is cleared, every portal with - // other_cell_id==0xFFFF gets DrawPortalPolyInternal(poly, false) — an - // invisible depth write at the portal plane, so interior geometry FARTHER - // than the doorway z-fails inside the aperture and the terrain seen through - // it keeps its pixels (#108). Wiring only — the draw lives in - // PortalDepthMaskRenderer. - private void DrawRetailPViewExitPortalSeal( + // BR-2: retail's invisible portal depth writes on every outside-leading + // portal (other_cell_id==0xFFFF) of this cell, clipped to the slice's view + // region — D3DPolyRender::DrawPortalPolyInternal (Ghidra 0x0059bc90), + // dispatched by PView::DrawCells (Ghidra 0x005a4840). The forceFarZ flag is + // retail's maxZ1(true)/maxZ2(false) selector: + // + // • INTERIOR root (forceFarZ=false → SEAL, true depth): after the full + // depth clear, stamp the door plane so interior geometry beyond the door + // z-fails inside the aperture and the terrain drawn through the outside + // view keeps its pixels (#108). + // • OUTDOOR root / look-in (forceFarZ=true → PUNCH, far depth): after the + // landscape + shell drew, erase the terrain depth inside the building's + // entry aperture so the flooded interior shows THROUGH the doorway + // against the nearer front-ground. Our pipeline draws the shell FIRST + // (as an outdoor entity in the landscape pass), so — unlike retail's + // shell-LAST order — we get the outside-the-aperture wall occlusion for + // free and need only the punch for in-aperture visibility (no reorder). + // + // Wiring only — the draw lives in PortalDepthMaskRenderer. + private void DrawRetailPViewPortalDepthWrite( AcDream.App.Rendering.RetailPViewCellSliceContext sliceCtx, - System.Numerics.Matrix4x4 viewProjection) + System.Numerics.Matrix4x4 viewProjection, + bool forceFarZ) { if (_portalDepthMask is null) return; @@ -9581,7 +9600,7 @@ public sealed class GameWindow : IDisposable for (int i = 0; i < cell.Portals.Count; i++) { if (cell.Portals[i].OtherCellId != 0xFFFF) - continue; // seals apply to portals leading OUTSIDE only + continue; // depth writes apply to portals leading OUTSIDE only if (i >= cell.PortalPolygons.Count) break; var localVerts = cell.PortalPolygons[i]; @@ -9592,7 +9611,7 @@ public sealed class GameWindow : IDisposable for (int v = 0; v < n; v++) world[v] = System.Numerics.Vector3.Transform(localVerts[v], cell.WorldTransform); - _portalDepthMask.DrawDepthFan(world[..n], viewProjection, sliceCtx.Slice.Planes, forceFarZ: false); + _portalDepthMask.DrawDepthFan(world[..n], viewProjection, sliceCtx.Slice.Planes, forceFarZ); } }