From bb9212c70d6a8f797d1245bce695c4409b32a808 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 20:06:23 +0200 Subject: [PATCH] fix(render): FW4 slice 6 - interior roots draw landscape unclipped Five ordering repairs later the cathedral falls still bled, and the final probe round proved why no ordering fix could ever land it: the falls now submit pre-clear with depth test LESS - and STILL pass, because the depth they test against is EMPTY. DrawWalkTerrainSlice scissored + hardware-clipped the terrain to the walk''s exit-view polygons, and at the #456 cathedral seam those authored portal polys are thin BANDS far narrower than the real opening: most of the vista never received terrain color or depth that frame (the visible scene there was stale color), so unclipped alpha painted straight across. Retail''s rule, per the PV campaign''s proven model and LScape::draw: landscape content is view-CULLED, never view-CLIPPED - terrain blocks draw whole, once per landscape turn (the views feed only CheckBlocks'' block visibility), and aperture exactness comes from the depth clear + exit seals + interior repaint that follow in the walk''s own order. This slice ports that: the driver fans exactly ONE terrain turn (correcting slice 1''s per-view fan), and an interior root''s terrain and sky draw unclipped/unscissored; the outdoor root keeps its single full-screen slice (equivalent by construction). The walk''s views still own the punch fans and dynamics apertures. Hermetic 6,762/0. Co-Authored-By: Claude Fable 5 --- .../RetailPViewPassExecutor.WalkLeaf.cs | 51 +++++++++++++++++++ .../Rendering/Walk/WalkFrameDriver.cs | 16 +++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs index e70af5cd..58d8e272 100644 --- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs +++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs @@ -18,6 +18,32 @@ internal sealed partial class RetailPViewPassExecutor { if (!frame.RenderSky) return; + + // FW4 slice 6: an INTERIOR root draws the sky ONCE, unclipped — + // the same LScape::draw rule as the terrain turn below (view-culled + // landscape content is never view-clipped; the clear + seals + + // interior repaint own aperture exactness). The former per-slice + // scissor left stale sky color wherever the authored exit-portal + // bands were narrower than the real opening. + if (!frame.RootCell.IsOutdoorNode) + { + _sky?.RenderSky( + frame.Camera, + frame.CameraWorldPosition, + frame.DayFraction, + frame.ActiveDayGroup, + frame.SkyKeyframe, + frame.EnvironOverrideActive); + if (_particles is not null && _particleRenderer is not null) + { + _particleRenderer.Draw( + frame.Camera, + frame.CameraWorldPosition, + ParticleRenderPass.SkyPreScene); + } + return; + } + ReadOnlySpan slices = clipAssembly.OutsideViewSlices; for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++) { @@ -58,6 +84,31 @@ internal sealed partial class RetailPViewPassExecutor nameof(sliceIndex), sliceIndex, $"walk terrain turn: slice {sliceIndex} of {slices.Length}"); } + + // FW4 slice 6: an INTERIOR root draws the terrain UNCLIPPED — + // retail's LScape::draw draws whole blocks (view-culled, never + // view-clipped; "retail never clips ordinary meshes per view"), + // and aperture exactness comes from the depth clear + exit seals + + // interior repaint that follow in the walk's own order. The former + // per-slice scissor+clip under-painted terrain COLOR AND DEPTH + // wherever the authored exit-portal polygons are narrower than the + // real opening (the #456 cathedral seam bands) — leaving stale + // color and empty depth that unclipped alpha (the falls) then + // painted straight across. The outdoor root keeps its single + // full-screen slice (equivalent to unclipped by construction). + if (!frame.RootCell.IsOutdoorNode) + { + _terrainDiagnostics.Begin(); + _terrain?.Draw( + frame.Camera, + frame.Frustum, + neverCullLandblockId: frame.PlayerLandblockId, + clipPlanes: default, + ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f)); + _terrainDiagnostics.Complete(); + return; + } + ClipViewSlice slice = slices[sliceIndex]; bool scissor = BeginDoorwayScissor(slice.NdcAabb); _surface.BindTerrainClip(); diff --git a/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs b/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs index 588b63f3..ad162c03 100644 --- a/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs +++ b/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs @@ -744,14 +744,14 @@ internal sealed class WalkFrameDriver : IWalkEventSink MarkIfGrown(); _events.Add(WalkFrameEvent.Sky()); _skyDrawnThisFrame = true; - // FW4 slice 1: the fan count is the WALK'S OWN active view count - // (carried on the Landscape event) — retail LScape::draw runs once - // per view the walk installed. The old clip apparatus's slice count - // no longer participates; the renderer re-derives the actual slice - // clip data from the same walk views before replay, so index i here - // and OutsideViewSlices[i] there are the same view by construction. - for (int slice = 0; slice < activeViewCount; slice++) - _events.Add(WalkFrameEvent.TerrainSlice(slice)); + // FW4 slice 6 (correcting slice 1's per-view fan): retail's + // LScape::draw draws the terrain blocks ONCE per landscape turn — + // the active views feed only the block-level visibility union + // (CheckBlocks); terrain cells are ordinary meshes and retail never + // clips those per view (pixel exactness = the depth clear + seals + + // interior repaint afterward). One terrain turn, always; the walk's + // views still own the punch fans and dynamics apertures. + _events.Add(WalkFrameEvent.TerrainSlice(0)); } private void HandleDrawCellsTurn(IReadOnlyList cells)