refactor(rendering): delete superseded visibility probes
Delete the callerless portal-BFS research graph and spent renderer probe families while retaining the production RetailFrameWalk path, terrain diagnostics, membership invariant, and walk transcript. Mutation evidence (all restored): 1. Restored PortalVisibilityBuilder type -> AppAssembly_ContainsNoSupersededPortalGraphTypes first failed Assert.Empty with AcDream.App.Rendering.PortalVisibilityBuilder. 2. Restored ACDREAM_PROBE_FACILITY_STAIRS -> ProductionSource_ContainsNoDeletedRendererProbe_AndRetainsWalkTranscriptProof first failed Assert.Empty on RenderingDiagnostics.cs. 3. Added a second RetailFrameWalk field -> WalkFrameOwners_AreUnique first failed Assert.Single with _frameWalk and _mutatedSecondFrameWalk. 4. Added OrderBy to OrderedStream -> OrderedWalkStream_HasNoCrossStreamReorder first failed Assert.DoesNotContain on OrderBy(. 5. Added IDatReaderWriter parameter -> FrameTimeWalkOwners_HaveNoRawDatDependency first failed Assert.Empty on RetailFrameWalk.MutatedRawDatParameter.
This commit is contained in:
parent
b77989c323
commit
bf53e2ad6e
82 changed files with 451 additions and 10108 deletions
|
|
@ -750,31 +750,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
private bool _disposing;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Per-cell-entity last-log frame number for rate-limiting the
|
||||
/// [indoor-walk] / [indoor-lookup] / [indoor-xform] / [indoor-cull]
|
||||
/// probes. Defaults to 30 frames at 30Hz = 1 sec.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ulong, int> _lastIndoorProbeFrame = new();
|
||||
private int _indoorProbeFrameCounter;
|
||||
private const int IndoorProbeRateLimitFrames = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true at most once per <see cref="IndoorProbeRateLimitFrames"/>
|
||||
/// frames per cellId. Caller must already have checked that an indoor
|
||||
/// probe flag is enabled.
|
||||
/// </summary>
|
||||
private bool ShouldEmitIndoorProbe(ulong cellId)
|
||||
{
|
||||
if (!_lastIndoorProbeFrame.TryGetValue(cellId, out int last)
|
||||
|| _indoorProbeFrameCounter - last >= IndoorProbeRateLimitFrames)
|
||||
{
|
||||
_lastIndoorProbeFrame[cellId] = _indoorProbeFrameCounter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Diagnostic counters logged once per ~5s under ACDREAM_WB_DIAG=1.
|
||||
private int _entitiesSeen;
|
||||
private int _entitiesDrawn;
|
||||
|
|
@ -947,16 +922,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
/// list. <see cref="Draw"/> reuses a per-dispatcher scratch field across frames to
|
||||
/// avoid the 480+ KB / frame GC pressure that the test-friendly overload incurs.
|
||||
/// Returns walk count via <paramref name="result"/>'s <c>EntitiesWalked</c> field.
|
||||
///
|
||||
/// <para>
|
||||
/// When <paramref name="indoorProbeState"/> is non-null the method emits
|
||||
/// <c>[indoor-cull]</c> lines for cell entities rejected by the
|
||||
/// visibleCellIds or frustum filters, and <c>[indoor-walk]</c> lines for
|
||||
/// cell entities that pass all filters. Rate-limited by
|
||||
/// <see cref="IndoorProbeState"/>. Pass <see langword="null"/> (the default)
|
||||
/// to disable all probe emission — used by the test-friendly
|
||||
/// <see cref="WalkEntities"/> overload.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static void WalkEntitiesInto(
|
||||
IEnumerable<LandblockEntry> landblockEntries,
|
||||
|
|
@ -966,7 +931,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
HashSet<uint>? animatedEntityIds,
|
||||
List<(WorldEntity Entity, int MeshRefIndex, uint LandblockId)> scratch,
|
||||
ref WalkResult result,
|
||||
IndoorProbeState? indoorProbeState = null,
|
||||
EntitySet set = EntitySet.All)
|
||||
{
|
||||
scratch.Clear();
|
||||
|
|
@ -1015,13 +979,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
if (!EntityMatchesSet(entity, set)) continue;
|
||||
if (entity.MeshRefs.Count == 0) continue;
|
||||
|
||||
// Detect cell entity for indoor probes — first MeshRef.GfxObjId
|
||||
// is an EnvCell id (low 16 bits ≥ 0x0100). Cheap to compute;
|
||||
// result reused for all probe checks below.
|
||||
ulong cellProbeId = (ulong)entity.MeshRefs[0].GfxObjId;
|
||||
bool isCellEntity = indoorProbeState is not null
|
||||
&& RenderingDiagnostics.IsEnvCellId(cellProbeId);
|
||||
|
||||
bool shellScoped = IsShellScopedSet(set)
|
||||
&& entity.IsBuildingShell
|
||||
&& visibleCellIds is not null;
|
||||
|
|
@ -1029,14 +986,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
if (!cellInVis)
|
||||
{
|
||||
if (shellScoped) result.BuildingShellAnchorReject++;
|
||||
if (isCellEntity && RenderingDiagnostics.ProbeIndoorCullEnabled
|
||||
&& indoorProbeState!.ShouldEmit(cellProbeId))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[indoor-cull] cellEnt=0x{entity.Id:X8} " +
|
||||
$"reason=visibleCellIds-miss " +
|
||||
$"parentCell=0x{entity.ParentCellId!.Value:X8}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (shellScoped) result.BuildingShellAnchorPass++;
|
||||
|
|
@ -1054,31 +1003,9 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
|
||||
if (!aabbVisible)
|
||||
{
|
||||
if (isCellEntity && RenderingDiagnostics.ProbeIndoorCullEnabled
|
||||
&& indoorProbeState!.ShouldEmit(cellProbeId))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[indoor-cull] cellEnt=0x{entity.Id:X8} " +
|
||||
$"reason=frustum " +
|
||||
$"aabbMin=({entity.AabbMin.X:F1},{entity.AabbMin.Y:F1},{entity.AabbMin.Z:F1}) " +
|
||||
$"aabbMax=({entity.AabbMax.X:F1},{entity.AabbMax.Y:F1},{entity.AabbMax.Z:F1})");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Passed all filters — emit walk probe.
|
||||
if (isCellEntity && RenderingDiagnostics.ProbeIndoorWalkEnabled
|
||||
&& indoorProbeState!.ShouldEmit(cellProbeId))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[indoor-walk] cellEnt=0x{entity.Id:X8} " +
|
||||
$"pos=({entity.Position.X:F1},{entity.Position.Y:F1},{entity.Position.Z:F1}) " +
|
||||
$"parentCell=0x{(entity.ParentCellId ?? 0u):X8} " +
|
||||
$"meshRef0=0x{cellProbeId:X8} " +
|
||||
$"meshRefCount={entity.MeshRefs.Count} " +
|
||||
$"landblockVisible=true aabbVisible=true cellInVis=true");
|
||||
}
|
||||
|
||||
result.EntitiesWalked++;
|
||||
for (int i = 0; i < entity.MeshRefs.Count; i++)
|
||||
scratch.Add((entity, i, entry.LandblockId));
|
||||
|
|
@ -1196,23 +1123,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
// that populates _walkScratch (a per-dispatcher field reused across frames)
|
||||
// instead of allocating a fresh List<(WorldEntity, int)> per frame.
|
||||
//
|
||||
// Pass an IndoorProbeState when any indoor probe is active so the static
|
||||
// WalkEntitiesInto can emit rate-limited [indoor-cull] / [indoor-walk]
|
||||
// lines without needing access to instance fields. Null = probes off.
|
||||
IndoorProbeState? probeState = null;
|
||||
if (RenderingDiagnostics.ProbeIndoorCullEnabled || RenderingDiagnostics.ProbeIndoorWalkEnabled)
|
||||
{
|
||||
// _currentFrame is snapped at construction time. Construct
|
||||
// once per Draw() call only — a second construction within
|
||||
// the same frame would stamp the dictionary with the
|
||||
// (already-advanced) counter value, suppressing the second
|
||||
// pass's emissions for IndoorProbeRateLimitFrames frames.
|
||||
// Today Draw() is called exactly once per frame; if a
|
||||
// future refactor adds a shadow / reflection / second pass,
|
||||
// this assumption needs revisiting.
|
||||
probeState = new IndoorProbeState(_lastIndoorProbeFrame, _indoorProbeFrameCounter);
|
||||
}
|
||||
|
||||
var walkResult = default(WalkResult);
|
||||
WalkEntitiesInto(
|
||||
ToEntries(landblockEntries),
|
||||
|
|
@ -1222,7 +1132,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
animatedEntityIds,
|
||||
_walkScratch,
|
||||
ref walkResult,
|
||||
probeState,
|
||||
set);
|
||||
_currentRenderSceneObserver?.ObserveDispatcherDraw(
|
||||
set,
|
||||
|
|
@ -1358,14 +1267,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
? new Vector2(lighting.Luminosity, lighting.Diffuse)
|
||||
: new Vector2(0f, 1f);
|
||||
|
||||
// #176 seam-draw probe: any entity parented to a target cell reports
|
||||
// its position + light set (a floor-coincident static/plate would be
|
||||
// the z-fight's second draw; the player entity is the positive
|
||||
// control). Before the culled-continue, like the dump above.
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeSeamDrawEnabled
|
||||
&& entity.ParentCell is { } seamPc
|
||||
&& AcDream.Core.Rendering.RenderingDiagnostics.SeamDrawTargetCells.Contains(seamPc))
|
||||
MaybeEmitSeamEnt(entity);
|
||||
}
|
||||
prevTupleEntityId = entity.Id;
|
||||
|
||||
|
|
@ -1468,41 +1369,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
|
||||
var renderData = _meshAdapter.TryGetRenderData(gfxObjId);
|
||||
|
||||
// [indoor-lookup] probe — emit once per cell entity per sec.
|
||||
// Fires BEFORE the null-renderData early-continue so a miss still
|
||||
// emits hit=false, distinguishing H2 (empty batches) from H6
|
||||
// (dispatcher fails to traverse Setup).
|
||||
ulong lookupCellId = (ulong)gfxObjId;
|
||||
if (RenderingDiagnostics.IsEnvCellId(lookupCellId)
|
||||
&& RenderingDiagnostics.ProbeIndoorLookupEnabled
|
||||
// Rate-limit in a separate namespace from [indoor-walk]/[indoor-cull]
|
||||
// (which key on the same gfxObjId). Without this, IndoorAll=1 would
|
||||
// silence the lookup probe whenever the walk probe fired first.
|
||||
&& ShouldEmitIndoorProbe(lookupCellId | 0x8000_0000_0000_0000UL))
|
||||
{
|
||||
bool hit = renderData is not null;
|
||||
bool isSetup = hit && renderData!.IsSetup;
|
||||
int partCount = isSetup ? renderData!.SetupParts.Count : 0;
|
||||
|
||||
int partsHit = 0, partsMiss = 0;
|
||||
if (isSetup)
|
||||
{
|
||||
foreach (var (partId, _) in renderData!.SetupParts)
|
||||
{
|
||||
if (_meshAdapter.TryGetRenderData(partId) is not null) partsHit++;
|
||||
else partsMiss++;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasEnvCellGeom = isSetup
|
||||
&& renderData!.SetupParts.Exists(t => (t.GfxObjId & 0x1_0000_0000UL) != 0);
|
||||
|
||||
Console.WriteLine(
|
||||
$"[indoor-lookup] cellId=0x{lookupCellId:X8} " +
|
||||
$"hit={hit} isSetup={isSetup} partCount={partCount} " +
|
||||
$"hasEnvCellGeom={hasEnvCellGeom} partsHit={partsHit} partsMiss={partsMiss}");
|
||||
}
|
||||
|
||||
if (renderData is null)
|
||||
{
|
||||
// Tier 1 cache (#53): mesh data is still async-decoding via
|
||||
|
|
@ -1595,23 +1461,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
var model = ComposePartWorldMatrix(
|
||||
entityWorld, meshRef.PartTransform, partTransform);
|
||||
|
||||
// [indoor-xform] probe — only for the cell's synthetic
|
||||
// geometry part (bit 32 set, per WB's PrepareEnvCellMeshData
|
||||
// cellGeomId convention). One line per part per sec.
|
||||
// Disambiguates hypothesis H5 (transform double-apply —
|
||||
// composedT lands at 2 × cellOrigin).
|
||||
if ((partGfxObjId & 0x1_0000_0000UL) != 0
|
||||
&& RenderingDiagnostics.ProbeIndoorXformEnabled
|
||||
&& ShouldEmitIndoorProbe(partGfxObjId))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[indoor-xform] cellGeomId=0x{partGfxObjId:X16} " +
|
||||
$"entityWorldT=({entityWorld.Translation.X:F2},{entityWorld.Translation.Y:F2},{entityWorld.Translation.Z:F2}) " +
|
||||
$"meshRefT=({meshRef.PartTransform.Translation.X:F2},{meshRef.PartTransform.Translation.Y:F2},{meshRef.PartTransform.Translation.Z:F2}) " +
|
||||
$"partT=({partTransform.Translation.X:F2},{partTransform.Translation.Y:F2},{partTransform.Translation.Z:F2}) " +
|
||||
$"composedT=({model.Translation.X:F2},{model.Translation.Y:F2},{model.Translation.Z:F2})");
|
||||
}
|
||||
|
||||
var restPose = partTransform * meshRef.PartTransform;
|
||||
|
||||
// #188 retail CPhysicsPart::Draw (0x0050d7a0) early-out: once a
|
||||
|
|
@ -1718,11 +1567,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
populateEntityId, populateLandblockId, _cache,
|
||||
_populateScratch, _populateSelectionScratch);
|
||||
|
||||
// S3 review fix round 1 (F5): the §4 flap [clip-route-disp] probe
|
||||
// (2026-06-10, throwaway) is deleted with the routing state it
|
||||
// reported on — it could only ever print "cullEnt=0" now that
|
||||
// ResolveSlotForFrame never culls anything.
|
||||
|
||||
ExecuteClassifiedGroups(
|
||||
vp,
|
||||
camPos,
|
||||
|
|
@ -1777,7 +1621,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
out Vector3 cameraWorldPosition)
|
||||
{
|
||||
_selectionLighting?.TickLighting();
|
||||
_indoorProbeFrameCounter++;
|
||||
viewProjection = camera.View * camera.Projection;
|
||||
_missRequested.Clear();
|
||||
|
||||
|
|
@ -3085,44 +2928,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
/// (AP-43) and docs/research/2026-06-19-lighting-a7-fixD-round2-*.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
// #176 seam-draw probe (ACDREAM_PROBE_SEAMDRAW) — throwaway apparatus. One
|
||||
// [seam-ent] line per target-cell entity, re-emitted on state change: world
|
||||
// position (F3 z — entities do NOT get the +0.02 shell lift), cull/slot,
|
||||
// and the SelectForObject light set resolved to identities (owner-cell
|
||||
// low16 + intensity). Sig dict is bounded by the handful of entities that
|
||||
// ever live in the target cells.
|
||||
private readonly Dictionary<ulong, string> _seamEntSigs = new();
|
||||
|
||||
private void MaybeEmitSeamEnt(in RenderInstanceCandidate entity)
|
||||
{
|
||||
var ci = System.Globalization.CultureInfo.InvariantCulture;
|
||||
var snap = _pointSnapshot;
|
||||
var sb = new System.Text.StringBuilder(200);
|
||||
sb.AppendFormat(ci,
|
||||
"guid=0x{0:X8} cell=0x{1:X8} pos=({2:F2},{3:F2},{4:F3}) culled={5} slot={6} indoor={7} L=[",
|
||||
entity.ServerGuid, entity.ParentCellId,
|
||||
entity.Position.X, entity.Position.Y, entity.Position.Z,
|
||||
_currentEntityCulled ? 1 : 0, _currentEntitySlot, _currentEntityIndoor ? 1 : 0);
|
||||
bool any = false;
|
||||
for (int k = 0; k < LightManager.MaxLightsPerObject; k++)
|
||||
{
|
||||
int idx = _currentEntityLightSet[k];
|
||||
if (idx < 0) continue;
|
||||
if (any) sb.Append(',');
|
||||
if (snap is not null && idx < snap.Count)
|
||||
sb.AppendFormat(ci, "{0:X4}:I{1:F0}", snap[idx].CellId & 0xFFFFu, snap[idx].Intensity);
|
||||
else
|
||||
sb.Append('?').Append(idx);
|
||||
any = true;
|
||||
}
|
||||
sb.Append(']');
|
||||
|
||||
string sig = sb.ToString();
|
||||
if (_seamEntSigs.TryGetValue(entity.Id, out var prev) && prev == sig) return;
|
||||
_seamEntSigs[entity.Id] = sig;
|
||||
Console.WriteLine($"[seam-ent] t={Environment.TickCount64} {sig}");
|
||||
}
|
||||
|
||||
private void ComputeEntityLightSet(
|
||||
in RenderInstanceCandidate entity)
|
||||
{
|
||||
|
|
@ -3632,41 +3437,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Thin wrapper around an instance's rate-limit dictionary + frame
|
||||
/// counter, passed into the static <see cref="WalkEntitiesInto"/>
|
||||
/// overload so it can emit rate-limited probe lines without access
|
||||
/// to instance fields. Null = probes disabled (test-friendly overload).
|
||||
/// </summary>
|
||||
internal sealed class IndoorProbeState
|
||||
{
|
||||
private readonly Dictionary<ulong, int> _lastFrame;
|
||||
private readonly int _currentFrame;
|
||||
private const int RateLimit = IndoorProbeRateLimitFrames;
|
||||
|
||||
internal IndoorProbeState(Dictionary<ulong, int> lastFrame, int currentFrame)
|
||||
{
|
||||
_lastFrame = lastFrame;
|
||||
_currentFrame = currentFrame;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true at most once per <see cref="IndoorProbeRateLimitFrames"/>
|
||||
/// frames per <paramref name="cellId"/>. Side-effect: stamps the frame
|
||||
/// number into the dictionary on success.
|
||||
/// </summary>
|
||||
internal bool ShouldEmit(ulong cellId)
|
||||
{
|
||||
if (!_lastFrame.TryGetValue(cellId, out int last)
|
||||
|| _currentFrame - last >= RateLimit)
|
||||
{
|
||||
_lastFrame[cellId] = _currentFrame;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class InstanceGroup
|
||||
{
|
||||
// Nonzero only while this exact object is registered in _groups.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue