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)