checkpoint: preserve cathedral look-in investigation state

This commit is contained in:
Erik 2026-08-30 23:29:28 +02:00
parent c287db8651
commit 572de1ec30
11 changed files with 757 additions and 197 deletions

View file

@ -110,6 +110,12 @@ public sealed class RetailPViewRenderer
private bool? _probeWalkRootPrevOutdoor;
private int _probeWalkRootFramesLeft;
private ulong _probeWalkRootFrame;
private string? _probeLookInState;
// The parked remote player's authoritative parent cell for the current
// cathedral FW4 investigation. This is diagnostic scope only: it changes
// no visibility or draw decision.
private const uint ProbeCathedralRemoteCellId = 0xF4180112u;
// Campaign FW3.2b-2: the walk's production world-data registries
// (published/retired by LandblockRenderPublisher) plus the per-frame
@ -263,6 +269,7 @@ public sealed class RetailPViewRenderer
// runs at append time, not at Replay time), so the world data must
// already be rebuilt for this frame before the walk starts.
Walk.WalkFrameDriver? walkDriver = null;
WalkProductionLeafRenderer? walkLeafRenderer = null;
if (walkActive)
{
Matrix4x4 view = ctx.CameraView;
@ -343,9 +350,12 @@ public sealed class RetailPViewRenderer
Action drawExitSeals = () =>
DrawWalkExitPortalMasks(ctx, passes, clipAssembly, walkDriver!);
var leafRenderer = new WalkProductionLeafRenderer(
walkLeafRenderer = new WalkProductionLeafRenderer(
walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals);
walkDriver = new Walk.WalkFrameDriver(walkExecutor!.Dispatcher, leafRenderer, _walkWorldData);
walkDriver = new Walk.WalkFrameDriver(
walkExecutor!.Dispatcher,
walkLeafRenderer,
_walkWorldData);
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
{
@ -527,7 +537,8 @@ public sealed class RetailPViewRenderer
pvFrame,
clipAssembly,
viewcone,
_lookInFrames,
walkActive ? walkDriver!.LookInCellTurns : [],
walkActive ? walkDriver : null,
outsideStageFlood,
ctx.Cells,
ctx.AnimatedEntityIds,
@ -539,6 +550,8 @@ public sealed class RetailPViewRenderer
frameViewBorrowed = true;
frameEntityPasses!.BeginEntityFrame(in frameView);
entityFrameOpen = true;
if (walkActive)
walkLeafRenderer!.BindEntityFrame(in frameView);
}
// The retained scene product is the production object source.
@ -651,6 +664,7 @@ public sealed class RetailPViewRenderer
clipAssembly,
capturedPartition,
capturedViewcone,
capturedDriver,
capturedPasses,
in capturedView);
};
@ -666,6 +680,7 @@ public sealed class RetailPViewRenderer
clipAssembly,
partition,
viewcone,
walkDriver!,
frameEntityPasses,
in frameView);
}
@ -1404,7 +1419,7 @@ public sealed class RetailPViewRenderer
_cellParticleOwnerScratch.Clear();
foreach (uint cellId in driver.VisitedCells)
{
if (_lookInCellIds.Contains(cellId))
if (driver.LookInCells.Contains(cellId))
continue;
UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch);
}
@ -1431,15 +1446,16 @@ public sealed class RetailPViewRenderer
/// split — the walk now draws every STATIC route (see
/// <see cref="DrawWalkDrivenStatics"/>); this method keeps ONLY what
/// stays on the OLD visibility pipeline per the plan's dual-compute
/// split: outdoor-cell unattached particles, LookInObject dynamics + their
/// per-cell particles, the late per-slice outside-dynamics/weather loop,
/// and the late particle union submission.</summary>
/// split: outdoor-cell unattached particles, the late per-slice outside-
/// dynamics/weather loop, and the late particle union submission. Look-in
/// dynamics and their cell particles are now walk-turn-owned.</summary>
private void DrawLandscapeDynamicsPhase(
RetailPViewFrameInput ctx,
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
InteriorEntityPartition.Result? partition,
ViewconeCuller viewcone,
Walk.WalkFrameDriver walkDriver,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
{
@ -1452,8 +1468,10 @@ public sealed class RetailPViewRenderer
// (the walk owns its own alpha barriers — WalkFrameDriver.OnBuildingTurn).
passes.DrawUnattachedSceneParticles(ctx, outdoorCells: true);
DrawBuildingLookInDynamics(
ctx, passes, clipAssembly, partition, frameEntityPasses, in frameView);
// Trace-only now. Look-in dynamics no longer have a late production
// phase: WalkFrameDriver replays each packed route at the walk's own
// per-cell DrawCells turn, before the enclosing building shell.
ProbeBuildingLookInFrames(ctx, passes, walkDriver);
// LATE phase (per slice): outside-stage dynamics' meshes + weather —
// unchanged from DrawLandscapeThroughOutsideView's own late loop.
@ -1527,129 +1545,72 @@ public sealed class RetailPViewRenderer
passes.UseIndoorMembershipOnlyRouting();
}
/// <summary>Campaign FW3.2b-2: the DYNAMICS-only remainder of the old
/// <see cref="DrawBuildingLookIns"/> — punches, shells, and look-in cell
/// STATICS are now walk-owned (<see cref="Walk.WalkFrameDriver"/>'s
/// Building/BuildingShell/LookInStatic turns); this method keeps ONLY the
/// LookInObject route (now dynamic-classified — see
/// <c>RenderScenePViewFrameBuilder.BuildLookInRoutes</c>) and the per-cell
/// particle union that route's owners feed, unioned with the walk's
/// static owners for that SAME cell (plan §FW3 item 4 — GetCellStatics
/// fills the gap the retired CellStatic-route particle submission left
/// for look-in cells specifically).</summary>
private void DrawBuildingLookInDynamics(
/// <summary>ACDREAM_PROBE_WALK_ROOT companion for FW4's surviving
/// through-wall dynamic. It compares the now-diagnostic-only legacy
/// look-in frames with the production walk's exact look-in set and reports
/// the two punch producers separately. Print-only; never participates in
/// admission.</summary>
private void ProbeBuildingLookInFrames(
RetailPViewFrameInput ctx,
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
InteriorEntityPartition.Result? partition,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
Walk.WalkFrameDriver walkDriver)
{
if (_lookInFrames.Count == 0)
if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
return;
int lookInRouteIndex = 0;
for (int frameIndex = 0; frameIndex < _lookInFrames.Count; frameIndex++)
uint[] walkLookInCells = walkDriver.LookInCells
.OrderBy(cellId => cellId)
.ToArray();
bool legacyHasTarget = _lookInFrames.Any(
frame => frame.OrderedVisibleCells.Contains(ProbeCathedralRemoteCellId));
bool walkHasTarget = Array.IndexOf(
walkLookInCells, ProbeCathedralRemoteCellId) >= 0;
string legacyShape = string.Join(
";",
_lookInFrames.Select((frame, index) =>
$"{index}:b{frame.SourceBuildingKey:x8}/lb{frame.SourceBuildingLandblockId:x8}"
+ $"/s{frame.ExteriorSeedPortals.Count}/c["
+ string.Join(",", frame.OrderedVisibleCells.Select(cell => cell.ToString("x8")))
+ "]"));
string walkShape = string.Join(",", walkLookInCells.Select(cell => cell.ToString("x8")));
int walkPunches = passes is RetailPViewPassExecutor concrete
? concrete.WalkLookInPunchCountThisFrame
: -1;
int legacyPunches = passes is RetailPViewPassExecutor concreteLegacy
? concreteLegacy.LegacyLookInPunchCountThisFrame
: -1;
string state =
$"viewer={ctx.ViewerCellId:x8}|root={ctx.RootCell.CellId:x8}"
+ $"|phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"
+ $"|legacy={legacyShape}|walk={walkShape}"
+ $"|p={walkPunches}/{legacyPunches}";
bool emit = state != _probeLookInState || _probeWalkRootFrame % 30 == 0;
_probeLookInState = state;
if (!emit)
return;
Console.WriteLine(
$"[lookin-frame] f={_probeWalkRootFrame} "
+ $"viewer=0x{ctx.ViewerCellId:X8} root=0x{ctx.RootCell.CellId:X8} "
+ $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase} "
+ $"legacyFrames={_lookInFrames.Count} legacy112={(legacyHasTarget ? 1 : 0)} "
+ $"walkLookIn={walkLookInCells.Length} walk112={(walkHasTarget ? 1 : 0)} "
+ $"punches=walk:{walkPunches},legacy:{legacyPunches}");
for (int index = 0; index < _lookInFrames.Count; index++)
{
PortalVisibilityFrame frame = _lookInFrames[frameIndex];
for (int i = frame.OrderedVisibleCells.Count - 1; i >= 0; i--)
{
uint cellId = frame.OrderedVisibleCells[i];
var clipKey = new LookInClipCell(frameIndex, cellId);
if (!clipAssembly.LookInCellToViewSlices.TryGetValue(
clipKey,
out ClipViewSlice[]? cellSlices)
|| cellSlices.Length == 0)
{
continue;
}
_cellStaticScratch.Clear();
if (partition is not null)
{
foreach (var e in partition.Dynamics)
if (e.ParentCellId == cellId)
_cellStaticScratch.Add(e);
}
bool cellDrewObjects = false;
_cellParticleUnionScratch.Clear();
foreach (ClipViewSlice slice in cellSlices)
{
int routeIndex = lookInRouteIndex++;
passes.UseCellPortalViewRouting(cellId, slice);
if (frameEntityPasses is not null)
{
RenderFrameRouteOwnerSelector.Replace(
_cellParticleOwnerScratch,
in frameView,
RenderFrameCandidateRoute.LookInObject,
routeIndex,
cellId);
}
else
{
ReplaceOwnerIds(
_cellParticleOwnerScratch,
_cellStaticScratch);
}
if (frameEntityPasses is not null
|| _cellStaticScratch.Count > 0)
{
_candidateObserver?.ObservePViewBucket(
CurrentRenderPViewRoute.LookInObject,
routeIndex,
cellId,
_cellStaticScratch);
_oneCell.Clear();
_oneCell.Add(cellId);
DrawEntityRouteOrLegacy(
ctx,
passes,
frameEntityPasses,
in frameView,
RenderFrameCandidateRoute.LookInObject,
routeIndex,
cellId,
_cellStaticScratch,
_oneCell);
cellDrewObjects = true;
_cellParticleUnionScratch.UnionWith(
_cellParticleOwnerScratch);
}
}
// The walk already drew this cell's STATIC content
// (WalkFrameDriver's LookInStatic turn) but never submits
// particles for it — GetCellStatics fills that gap, unioned
// with the dynamic route's own owners so ONE
// DrawCellParticles call covers both.
if (_walkWorldData is not null)
{
Walk.WalkFrameStaticRecords statics =
_walkWorldData.GetCellStatics(cellId);
foreach (RenderProjectionRecord record in statics.Records)
{
if (record.Source.LocalEntityId != 0)
{
_cellParticleUnionScratch.Add(record.Source.LocalEntityId);
cellDrewObjects = true;
}
}
}
if (cellDrewObjects)
{
passes.DrawCellParticles(ctx, new RetailPViewCellSliceContext(
cellId, NoClipSlice, _cellParticleUnionScratch));
}
}
passes.UseIndoorMembershipOnlyRouting();
PortalVisibilityFrame frame = _lookInFrames[index];
Console.WriteLine(
$"[lookin-frame] f={_probeWalkRootFrame} index={index} "
+ $"building=0x{frame.SourceBuildingKey:X8} "
+ $"landblock=0x{frame.SourceBuildingLandblockId:X8} "
+ $"seeds={frame.ExteriorSeedPortals.Count} "
+ $"has112={(frame.OrderedVisibleCells.Contains(ProbeCathedralRemoteCellId) ? 1 : 0)} "
+ $"cells=[{string.Join(",", frame.OrderedVisibleCells.Select(cell => $"0x{cell:X8}"))}]");
}
Console.WriteLine(
$"[lookin-frame] f={_probeWalkRootFrame} walkCells=["
+ string.Join(",", walkLookInCells.Select(cell => $"0x{cell:X8}"))
+ "]");
}
private void DrawLandscapeThroughOutsideView(