feat(rendering): complete current-path render referee

Extend the non-drawing oracle through ordered PView routes, dispatcher visibility and final instance payloads, and accepted retail selection parts. Lifecycle artifacts can now referee the later shadow scene without influencing production visibility or draw decisions.
This commit is contained in:
Erik 2026-07-24 21:28:12 +02:00
parent dbaea19269
commit f9b68f8f2a
11 changed files with 964 additions and 41 deletions

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AcDream.App.Rendering.Scene;
using AcDream.Core.World;
namespace AcDream.App.Rendering;
@ -13,6 +14,7 @@ namespace AcDream.App.Rendering;
public sealed class RetailPViewRenderer
{
private readonly InteriorEntityPartition.IObserver? _partitionObserver;
private readonly ICurrentRenderPViewObserver? _candidateObserver;
private readonly PortalVisibilityFrame _mainPortalFrameScratch = new();
private readonly ClipFrameAssembly _clipAssemblyScratch = new();
private readonly ViewconeCuller _viewconeScratch = new();
@ -72,6 +74,7 @@ public sealed class RetailPViewRenderer
InteriorEntityPartition.IObserver? partitionObserver)
{
_partitionObserver = partitionObserver;
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
}
// T2 (BR-4): retail has NO distance constant on the flood-admission chain
@ -198,41 +201,51 @@ public sealed class RetailPViewRenderer
ctx.ViewProjection,
_viewconeScratch);
// #118: stage assignment for dynamics under an INTERIOR root. Retail
// draws the OUTSIDE world's objects inside the landscape stage —
// PView::DrawCells runs LScape::draw FIRST (pc:432719), then the gated
// full depth clear (pc:432731-432732) and the exit-portal SEALS
// (pc:432785-432786); DrawBlock draws every landcell's objects via
// DrawSortCell (0x005a17c0, pc:430124). A dynamic deferred to our
// single last pass instead z-fails against the seal's true-depth stamp
// the moment it stands beyond the door plane — the house-exit
// clip+vanish (pinned by HouseExitWalkReplayTests). So under an
// interior root: outdoor-classified dynamics draw in the outside
// stage; an indoor dynamic whose sphere STRADDLES an exit portal
// draws in BOTH stages (retail's per-overlapped-cell shadow-part
// draw, DrawBlock pc:430056-430064) so neither body half clips at the
// plane. Outdoor roots keep ALL dynamics in the last pass — our
// z-buffered equivalent of retail's painter-ordered outdoor pass (the
// BR-2 punch-after-dynamics lesson, reverted 88be519).
_outsideStageDynamics.Clear();
if (!ctx.RootCell.IsOutdoorNode)
_candidateObserver?.BeginPViewFrame();
try
{
foreach (var e in partition.Dynamics)
// #118: stage assignment for dynamics under an INTERIOR root. Retail
// draws the OUTSIDE world's objects inside the landscape stage —
// PView::DrawCells runs LScape::draw FIRST (pc:432719), then the gated
// full depth clear (pc:432731-432732) and the exit-portal SEALS
// (pc:432785-432786); DrawBlock draws every landcell's objects via
// DrawSortCell (0x005a17c0, pc:430124). A dynamic deferred to our
// single last pass instead z-fails against the seal's true-depth stamp
// the moment it stands beyond the door plane — the house-exit
// clip+vanish (pinned by HouseExitWalkReplayTests). So under an
// interior root: outdoor-classified dynamics draw in the outside
// stage; an indoor dynamic whose sphere STRADDLES an exit portal
// draws in BOTH stages (retail's per-overlapped-cell shadow-part
// draw, DrawBlock pc:430056-430064) so neither body half clips at the
// plane. Outdoor roots keep ALL dynamics in the last pass — our
// z-buffered equivalent of retail's painter-ordered outdoor pass (the
// BR-2 punch-after-dynamics lesson, reverted 88be519).
_outsideStageDynamics.Clear();
if (!ctx.RootCell.IsOutdoorNode)
{
EntitySphere(e, out var c, out float r);
if (DynamicDrawsInOutsideStage(e.ParentCellId, c, r, drawableCells, ctx.Cells))
_outsideStageDynamics.Add(e);
foreach (var e in partition.Dynamics)
{
EntitySphere(e, out var c, out float r);
if (DynamicDrawsInOutsideStage(e.ParentCellId, c, r, drawableCells, ctx.Cells))
_outsideStageDynamics.Add(e);
}
}
DrawLandscapeThroughOutsideView(ctx, passes, clipAssembly, partition, viewcone);
passes.UseIndoorMembershipOnlyRouting();
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
DrawEnvCellShells(passes, pvFrame);
DrawCellObjectLists(ctx, passes, pvFrame, clipAssembly, drawableCells, partition, viewcone);
DrawDynamicsLast(ctx, passes, partition, viewcone, ctx.RootCell.IsOutdoorNode);
_candidateObserver?.CompletePViewFrame();
return result;
}
catch
{
_candidateObserver?.AbortPViewFrame();
throw;
}
DrawLandscapeThroughOutsideView(ctx, passes, clipAssembly, partition, viewcone);
passes.UseIndoorMembershipOnlyRouting();
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
DrawEnvCellShells(passes, pvFrame);
DrawCellObjectLists(ctx, passes, pvFrame, clipAssembly, drawableCells, partition, viewcone);
DrawDynamicsLast(ctx, passes, partition, viewcone, ctx.RootCell.IsOutdoorNode);
return result;
}
// R-A2: group the nearby building cells by BuildingId and run one per-building flood per group
@ -435,6 +448,11 @@ public sealed class RetailPViewRenderer
if (_cellStaticScratch.Count > 0)
{
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.LookInObject,
i,
cellId,
_cellStaticScratch);
passes.DrawEntityBucket(ctx, _cellStaticScratch, _oneCell);
// The cell-particles pass for look-in cells — retail's
@ -489,6 +507,11 @@ public sealed class RetailPViewRenderer
if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
_outdoorStaticScratch.Add(e);
}
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.LandscapeOutdoorStatic,
probeSliceIndex,
0,
_outdoorStaticScratch);
probeSliceIndex++;
passes.DrawLandscapeSlice(ctx, new RetailPViewLandscapeSliceContext(slice, _outdoorStaticScratch));
}
@ -541,6 +564,11 @@ public sealed class RetailPViewRenderer
probeSliceIndex,
_outsideStageDynamics,
viewcone);
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.LandscapeOutsideDynamic,
probeSliceIndex,
0,
_outdoorStaticScratch);
probeSliceIndex++;
passes.DrawLandscapeSliceLate(ctx, new RetailPViewLandscapeLateSliceContext(
slice, _outdoorStaticScratch, _lateParticleOwnerScratch));
@ -693,6 +721,11 @@ public sealed class RetailPViewRenderer
if (_dynamicsScratch.Count == 0)
return;
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.DynamicLast,
0,
0,
_dynamicsScratch);
passes.UseIndoorMembershipOnlyRouting();
passes.DrawEntityBucket(ctx, _dynamicsScratch, visibleCellIds: null);
@ -779,6 +812,11 @@ public sealed class RetailPViewRenderer
// cull; only the draw is batched).
if (_allCellStatics.Count > 0)
{
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.CellStatic,
0,
0,
_allCellStatics);
passes.UseIndoorMembershipOnlyRouting();
passes.DrawEntityBucket(ctx, _allCellStatics, _cellObjCells);
}