From 7f500b97a3869d78dd3601a1ad57242a13ad44c5 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 20:50:52 +0200 Subject: [PATCH] revert(render): FW4 slices 4+5 - unanimous tri-review verdict Reverts 0d6cd5c0 (slice 5) and f3a03efc (slice 4). All three independent cathedral reviews (fable, opus, external) plus the DAT geometry ground truth condemned both: - Slice 5 misidentified its targets: owners 0x4F418012-15 are cell 0xF4180106''s TORCHES + a prop + a plant (InteriorEntityIdAllocator namespace), not the falls - the real waterfall emitters are outdoor landblock objects in the 0xC namespace (0xCF418000-13). Moving torch flames pre-clear is the owner-reported in-cathedral particle regression. - Slice 4''s straddle rule is invented (retail stages by actual shadow-cell membership), and its "emit once" contradicts retail''s deliberate mid-frame m_nFrameStamp re-arm @0x005a4886 (a part overlapping both scopes legitimately draws twice). The synthesis of all seven review reports and the adjudicated fix plan live in docs/research/2026-08-30-cathedral-synthesis.md (next commit). Hermetic 6,762/0. Co-Authored-By: Claude Fable 5 --- src/AcDream.App/Rendering/ParticleRenderer.cs | 20 ---- .../Rendering/RetailPViewRenderer.cs | 111 +----------------- src/AcDream.App/Rendering/Walk/WalkPView.cs | 14 --- 3 files changed, 3 insertions(+), 142 deletions(-) diff --git a/src/AcDream.App/Rendering/ParticleRenderer.cs b/src/AcDream.App/Rendering/ParticleRenderer.cs index 694955e2..75f60578 100644 --- a/src/AcDream.App/Rendering/ParticleRenderer.cs +++ b/src/AcDream.App/Rendering/ParticleRenderer.cs @@ -161,10 +161,6 @@ public sealed unsafe partial class ParticleRenderer : IDisposable private readonly List _meshRunScratch = new(64); private readonly List _submissionScratch = new(128); private readonly List _scopedEmitterScratch = new(64); - - // ACDREAM_PROBE_WALK_ROOT companion (throwaway): rate limiter for the - // [walk-emit] interior-unattached emitter dump. - private uint _probeEmitterDumpCounter; private readonly List _deferredAlpha = new(128); private DeferredParticleDraw[] _preparedAlpha = new DeferredParticleDraw[256]; private uint[] _preparedInstanceOffsets = new uint[256]; @@ -251,22 +247,6 @@ public sealed unsafe partial class ParticleRenderer : IDisposable _scopedEmitterScratch, excludedAttachedOwnerIds, unattachedCellScope); - // ACDREAM_PROBE_WALK_ROOT companion (throwaway): identify WHICH - // emitters ride the interior-unattached route (the falls hunt) — - // owner cell, anchor position, attachment. - if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled - && unattachedCellScope == UnattachedEmitterCellScope.InteriorCells - && ++_probeEmitterDumpCounter % 120 == 0) - { - foreach (var em in _scopedEmitterScratch) - { - Console.WriteLine( - $"[walk-emit] cell={em.OwnerCellId:x8} attached={em.AttachedObjectId:x} " - + $"anchor=({em.AnchorPos.X:F1},{em.AnchorPos.Y:F1},{em.AnchorPos.Z:F1}) " - + $"active={em.ActiveCount} " - + $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"); - } - } Matrix4x4.Invert(camera.View, out Matrix4x4 invView); Vector3 cameraRight = Vector3.Normalize(new Vector3(invView.M11, invView.M12, invView.M13)); Vector3 cameraUp = Vector3.Normalize(new Vector3(invView.M21, invView.M22, invView.M23)); diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 077955e8..d47d5778 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -71,28 +71,6 @@ public sealed class RetailPViewRenderer private readonly HashSet _cellParticleOwnerScratch = new(); private readonly HashSet _dynamicParticleOwnerScratch = new(); - // FW4 slice 4: owners whose particles ALREADY submitted in the - // pre-clear outside stage this frame (interior roots only). The last - // pass subtracts them — retail submits an emitter's polys at its - // object's FIRST draw (the landscape stage for anything overlapping - // outdoor shadow cells) and drains them at the pre-clear boundary - // flush over true landscape depth; a straddler re-emitted post-clear - // has no outdoor depth left to occlude it (the cathedral falls - // shine-through, fourth surface). - private readonly HashSet _preClearParticleOwnerScratch = new(); - - // FW4 slice 5: interior flood-cell STATICS whose bounds straddle their - // cell's exit-portal plane (the cathedral falls — EnvCell stab-list - // objects hanging out over the lake). Retail draws such an object - // during LScape::draw via the outdoor shadow-cell lists (DrawBlock - // pc:430056-430064, once per overlapped shadow cell), so its particles - // submit in the landscape scope and drain at the pre-clear boundary - // flush over true terrain depth; the cell's own post-clear turn skips - // them (drawn-once). Computed after Collect from the walk's flood; - // submitted by the pre-clear closure; subtracted from the post-replay - // cell-owners union. - private readonly HashSet _preClearCellStaticOwnerScratch = new(); - // MP-Alloc (2026-07-05): the frame's entity partition (ByCell/OutdoorStatic/ // Dynamics), reused across frames instead of `new`ing a Result (a Dictionary // + 2 Lists, plus one List per visible cell) every DrawInside @@ -177,7 +155,6 @@ public sealed class RetailPViewRenderer passes.BeginFrame(); RecycleLookInFrames(); ResetBuildingGroups(); - _preClearParticleOwnerScratch.Clear(); var pvFrame = PortalVisibilityBuilder.Build( ctx.RootCell, @@ -366,15 +343,9 @@ public sealed class RetailPViewRenderer walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals); walkDriver = new Walk.WalkFrameDriver(walkExecutor!.Dispatcher, leafRenderer, _walkWorldData); - if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) - { - AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame = - _probeWalkRootFrame % 90 == 0; - } walkDriver.Collect( _frameWalk, ctx.ViewerCellId, walkCameraCell, walkLandscape, walkContext, ctx.ViewProjection, ctx.CameraWorldPosition); - AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame = false; // FW4 slice 1: an interior root's terrain/sky/punch clip slices // come from THE WALK'S OWN outside_view — retail's one @@ -594,37 +565,6 @@ public sealed class RetailPViewRenderer } } - // FW4 slice 5: classify the interior flood cells' STATICS by the - // exit-plane straddle test — a straddler's particles (the - // cathedral falls: EnvCell stab objects hanging over the lake) - // submit pre-clear like retail's outdoor shadow-cell draw; see - // _preClearCellStaticOwnerScratch's field comment. GetCellStatics - // is per-frame cached, so this also pre-warms the post-replay - // cell-owners pass. - _preClearCellStaticOwnerScratch.Clear(); - if (walkActive && !ctx.RootCell.IsOutdoorNode) - { - foreach (uint cellId in walkDriver!.VisitedCells) - { - if ((cellId & 0xFFFFu) < 0x100u || _lookInCellIds.Contains(cellId)) - continue; - LoadedCell? staticCell = ctx.Cells.Find(cellId); - if (staticCell is null) - continue; - Walk.WalkFrameStaticRecords cellRecords = - _walkWorldData!.GetCellStatics(cellId); - foreach (RenderProjectionRecord record in cellRecords.Records) - { - if (record.Source.LocalEntityId == 0) - continue; - Vector3 c = (record.Bounds.Minimum + record.Bounds.Maximum) * 0.5f; - float r = (record.Bounds.Maximum - record.Bounds.Minimum).Length() * 0.5f; - if (SphereStraddlesExitPlane(staticCell, c, r)) - _preClearCellStaticOwnerScratch.Add(record.Source.LocalEntityId); - } - } - } - if (walkActive) { // Campaign FW3.2b-2: the walk owns every static draw — @@ -657,17 +597,6 @@ public sealed class RetailPViewRenderer // BEFORE the pre-clear drain so they composite against // still-true landscape depth, never the seals. SubmitWalkLandscapeStaticParticles(ctx, walkExecutor!, capturedDriver); - // FW4 slice 5: straddling flood-cell statics' emitters - // (the falls) — retail's shadow-cell landscape draw. - if (_preClearCellStaticOwnerScratch.Count > 0) - { - _preClearParticleOwnerScratch.UnionWith( - _preClearCellStaticOwnerScratch); - passes.DrawLandscapeStaticParticles( - ctx, - new RetailPViewLandscapeStaticParticleContext( - _preClearCellStaticOwnerScratch)); - } passes.UseIndoorMembershipOnlyRouting(); DrawLandscapeDynamicsPhase( ctx, @@ -1463,10 +1392,6 @@ public sealed class RetailPViewRenderer continue; UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch); } - // FW4 slice 5: exit-plane straddlers (the falls) already emitted at - // the pre-clear boundary — retail's shadow-cell landscape draw; - // every owner emits exactly once. - _cellParticleOwnerScratch.ExceptWith(_preClearCellStaticOwnerScratch); if (_cellParticleOwnerScratch.Count > 0) { passes.DrawCellParticles( @@ -1568,26 +1493,13 @@ public sealed class RetailPViewRenderer }); } - // Late-particle union submission. OUTDOOR root: DynamicLast owners - // excluded (both stages drain at the same final flush there — a - // duplicate submission would double-composite), matching - // DrawLandscapeThroughOutsideView's own final submission. INTERIOR - // root (FW4 slice 4): the outside-stage owners — exit-plane - // STRADDLERS included — submit HERE, pre-clear, and the last pass - // subtracts them instead: retail submits an emitter's polys at its - // object's FIRST draw (the landscape stage) and drains them at the - // pre-clear boundary flush over true landscape depth; the former - // except-here/emit-last placement left a straddler's outside half - // with no depth to occlude it after the clear (the cathedral falls - // bleeding through terrain and water, probe-pinned: - // outside=1 cone=1 yet phase=post). - if (frameEntityPasses is not null && ctx.RootCell.IsOutdoorNode) + // Late-particle union submission — DynamicLast owners excluded, same + // as DrawLandscapeThroughOutsideView's own final submission. + if (frameEntityPasses is not null) { RenderFrameRouteOwnerSelector.ExceptRoute( _staticParticleUnionScratch, in frameView, RenderFrameCandidateRoute.DynamicLast); } - if (!ctx.RootCell.IsOutdoorNode) - _preClearParticleOwnerScratch.UnionWith(_staticParticleUnionScratch); if (_staticParticleUnionScratch.Count > 0) { passes.DrawLandscapeStaticParticles( @@ -2245,10 +2157,6 @@ public sealed class RetailPViewRenderer RenderFrameCandidateRoute.DynamicLast, 0, 0); - // FW4 slice 4: owners already emitted pre-clear (the outside - // stage's late union — straddlers included) emit ONCE; see - // _preClearParticleOwnerScratch's field comment. - _dynamicParticleOwnerScratch.ExceptWith(_preClearParticleOwnerScratch); passes.UseIndoorMembershipOnlyRouting(); DrawEntityRouteOrLegacy( @@ -2383,9 +2291,6 @@ public sealed class RetailPViewRenderer if (InteriorEntityPartition.IsIndoorCellId(e.ParentCellId)) _dynamicParticleOwnerScratch.Add(e.Id); } - // FW4 slice 4: pre-clear-emitted owners (straddlers) emit once — - // see _preClearParticleOwnerScratch's field comment. - _dynamicParticleOwnerScratch.ExceptWith(_preClearParticleOwnerScratch); if (_dynamicParticleOwnerScratch.Count > 0) passes.DrawDynamicsParticles(ctx, _dynamicParticleOwnerScratch); } @@ -2681,16 +2586,6 @@ public sealed class RetailPViewRenderer if (cell is null) return false; - return SphereStraddlesExitPlane(cell, sphereCenter, sphereRadius); - } - - /// The exit-plane straddle test shared by the #118 dynamic - /// stage split and the FW4 slice-5 cell-static particle split — retail - /// draws a straddling object once per overlapped outdoor shadow cell - /// inside LScape::draw (DrawBlock pc:430056-430064). - internal static bool SphereStraddlesExitPlane( - LoadedCell cell, Vector3 sphereCenter, float sphereRadius) - { var localC = Vector3.Transform(sphereCenter, cell.InverseWorldTransform); int n = Math.Min(cell.Portals.Count, cell.ClipPlanes.Count); for (int i = 0; i < n; i++) diff --git a/src/AcDream.App/Rendering/Walk/WalkPView.cs b/src/AcDream.App/Rendering/Walk/WalkPView.cs index faeb9859..3e5f7984 100644 --- a/src/AcDream.App/Rendering/Walk/WalkPView.cs +++ b/src/AcDream.App/Rendering/Walk/WalkPView.cs @@ -119,14 +119,6 @@ public sealed class WalkPView flags.InView = true; // portal polygon faces the viewer } } - if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame) - { - Console.WriteLine( - $"[walk-portal] init cell={cell.CellId:x8} i={i} " - + $"dest={cell.Portals[i].OtherCellId:x8} d={d:F3} " - + $"pside={cell.Portals[i].PortalSide} " - + $"inview={(flags.InView ? 1 : 0)}"); - } } if (flags.InView) @@ -209,12 +201,6 @@ public sealed class WalkPView cell, portal.PortalSide, cell.PortalPolygons[portal.PolygonIndex], doClip: true, ctx, _clipScratch); - if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame) - { - Console.WriteLine( - $"[walk-portal] clip cell={cell.CellId:x8} view={i} j={j} " - + $"dest={portal.OtherCellId:x8} n={n}"); - } if (n == 0) continue; if (portal.OtherCellId == 0xFFFFFFFFu)