fix(render): DynamicLast admits only the walk''s own root flood
The [dyn-route] trace pinned the through-wall remote player (visible from 0xF4180101 AND outdoors while parented at 0xF4180112 - a cell with no retail sightline chain from either): the legacy visibility builder invents cross-building cell views at the cathedral (REAL 3-5-plane cones for 0x112, not the zero-plane trapdoor), the viewcone admits his sphere, and he rides the last dynamics pass - post-clear on interior roots (walls'' depth wiped) and post-world outdoors. The stage-set split (synthesis plan step 4): an interior-parented dynamic may ride DynamicLast ONLY when its parent cell is in THE WALK''S OWN ROOT FLOOD (oracle-trace-conformant; retail draws look-in occupants inside the landscape stage through the composed portal chain, and unreachable cells'' occupants not at all). The frame product''s build input gains RootFloodCells (the driver''s InteriorFloodCells as a per-frame set); non-walk/diagnostic frames and the comparison wrapper keep the legacy drawableCells meaning. The [dyn-route] probe logs the new rootflood-excluded state. Hermetic 6,762/0; InstalledDat walk conformance 40/1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4686a1ac74
commit
fc2e6b79bc
3 changed files with 61 additions and 7 deletions
|
|
@ -71,6 +71,10 @@ public sealed class RetailPViewRenderer
|
|||
private readonly HashSet<uint> _cellParticleOwnerScratch = new();
|
||||
private readonly HashSet<uint> _dynamicParticleOwnerScratch = new();
|
||||
|
||||
// The walk's TRUE root flood as a set, rebuilt per frame for the
|
||||
// DynamicLast stage gate (the stage-set split — synthesis plan step 4).
|
||||
private readonly HashSet<uint> _rootFloodSetScratch = 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<WorldEntity> per visible cell) every DrawInside
|
||||
|
|
@ -507,6 +511,16 @@ public sealed class RetailPViewRenderer
|
|||
HashSet<uint> outsideStageFlood =
|
||||
walkActive ? walkDriver!.VisitedCells : drawableCells;
|
||||
|
||||
// The stage-set split: the walk's TRUE root flood (never the
|
||||
// visited union) keys which interior-parented dynamics may ride
|
||||
// the last pass — see BuildOutsideDynamicRoutes' sibling gate.
|
||||
_rootFloodSetScratch.Clear();
|
||||
if (walkActive)
|
||||
{
|
||||
foreach (uint cellId in walkDriver!.InteriorFloodCells)
|
||||
_rootFloodSetScratch.Add(cellId);
|
||||
}
|
||||
|
||||
if (_sceneFrameProduct is not null)
|
||||
{
|
||||
frameView = _sceneFrameProduct.BuildAndBorrow(
|
||||
|
|
@ -517,7 +531,11 @@ public sealed class RetailPViewRenderer
|
|||
outsideStageFlood,
|
||||
ctx.Cells,
|
||||
ctx.AnimatedEntityIds,
|
||||
ctx.RootCell.IsOutdoorNode);
|
||||
ctx.RootCell.IsOutdoorNode,
|
||||
// Non-walk frames (diagnostic fakes / legacy fallback)
|
||||
// keep the pre-split admission: drawableCells WAS the
|
||||
// flood in the legacy meaning.
|
||||
walkActive ? _rootFloodSetScratch : drawableCells);
|
||||
frameViewBorrowed = true;
|
||||
frameEntityPasses!.BeginEntityFrame(in frameView);
|
||||
entityFrameOpen = true;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ internal readonly record struct RenderScenePViewBuildInput(
|
|||
HashSet<uint> DrawableCells,
|
||||
IRetailPViewCellSource Cells,
|
||||
HashSet<uint>? AnimatedEntityIds,
|
||||
bool RootIsOutdoor);
|
||||
bool RootIsOutdoor,
|
||||
IReadOnlySet<uint> RootFloodCells);
|
||||
|
||||
internal readonly record struct RenderFrameProductComparisonSnapshot(
|
||||
bool Enabled,
|
||||
|
|
@ -188,7 +189,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
HashSet<uint> drawableCells,
|
||||
IRetailPViewCellSource cells,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
bool rootIsOutdoor)
|
||||
bool rootIsOutdoor,
|
||||
IReadOnlySet<uint> rootFloodCells)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(portalFrame);
|
||||
ArgumentNullException.ThrowIfNull(clipAssembly);
|
||||
|
|
@ -215,7 +217,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
drawableCells,
|
||||
cells,
|
||||
animatedEntityIds,
|
||||
rootIsOutdoor);
|
||||
rootIsOutdoor,
|
||||
rootFloodCells);
|
||||
_builder.Build(_exchange, frameSequence, in input);
|
||||
return _exchange.BorrowLatest(
|
||||
scene.Generation,
|
||||
|
|
@ -235,8 +238,12 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
HashSet<uint>? animatedEntityIds,
|
||||
uint tupleLandblockId,
|
||||
bool rootIsOutdoor,
|
||||
Vector3 cameraWorldPosition = default)
|
||||
Vector3 cameraWorldPosition = default,
|
||||
IReadOnlySet<uint>? rootFloodCells = null)
|
||||
{
|
||||
// Comparison/diagnostic path: absent an explicit walk root flood,
|
||||
// drawableCells keeps the pre-stage-split admission (drawableCells
|
||||
// WAS the flood in the legacy meaning these fixtures pin).
|
||||
RenderFrameView view = BuildAndBorrow(
|
||||
portalFrame,
|
||||
clipAssembly,
|
||||
|
|
@ -245,7 +252,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
drawableCells,
|
||||
cells,
|
||||
animatedEntityIds,
|
||||
rootIsOutdoor);
|
||||
rootIsOutdoor,
|
||||
rootFloodCells ?? drawableCells);
|
||||
try
|
||||
{
|
||||
Compare(
|
||||
|
|
@ -1417,6 +1425,33 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
continue;
|
||||
}
|
||||
|
||||
// The stage-set split (synthesis plan step 4, [dyn-route]-pinned
|
||||
// 2026-08-30): an interior-parented dynamic rides the LAST pass
|
||||
// ONLY when its parent cell is in THE WALK'S OWN ROOT FLOOD.
|
||||
// Retail draws a look-in cell's occupants inside the landscape
|
||||
// stage through the composed portal chain, and an unreachable
|
||||
// cell's occupants not at all — the legacy visibility builder
|
||||
// invents cross-building cell views at the cathedral (real
|
||||
// 3-5-plane cones for 0xF4180112 from roots with no sightline),
|
||||
// which let a remote player draw post-clear/post-world through
|
||||
// opaque walls. The walk's flood is oracle-trace-conformant;
|
||||
// key the route on it.
|
||||
if (indoor && !input.RootFloodCells.Contains(parentCellId!.Value))
|
||||
{
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
{
|
||||
string state = $"parent={parentCellId.Value:x8} rootflood-excluded";
|
||||
if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev)
|
||||
|| prev != state)
|
||||
{
|
||||
_probeDynRouteStates[record.Source.LocalEntityId] = state;
|
||||
Console.WriteLine(
|
||||
$"[dyn-route] id={record.Source.LocalEntityId:x} {state}");
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
bool visible = indoor
|
||||
? input.Viewcone.SphereVisibleInCell(
|
||||
|
|
|
|||
|
|
@ -162,7 +162,8 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[Cell],
|
||||
EmptyCellSource.Instance,
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
RootIsOutdoor: true,
|
||||
RootFloodCells: new HashSet<uint> { Cell });
|
||||
builder.Build(exchange, frameSequence, in input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue