refactor(render): Phase U.1 — delete two-pipe inside-out machinery

Remove IndoorCellStencilPipeline + portal_stencil shaders, RenderInsideOutAcdream,
RenderOutsideInAcdream, the A8-perf instrumentation, the cameraInsideBuilding /
ACDREAM_A8_INDOOR_BRANCH branch, and the dead EntitySet partition values. Collapse
the render branch to the default Draw(All) path (U.4a replaces it with the gated
unified pass). Keep all audited EnvCellRenderer / BuildingLoader / CellVisibility /
camera-collision fixes.

Also deleted with the partition: the two test-only walk helpers
(WbDrawDispatcher.WalkEntitiesForTest / WalkEntitiesForTestByCellIds) and their
test files (WbDrawDispatcherEntitySetTests, WbDrawDispatcherCellIdsOverloadTests),
which existed solely to exercise the removed IndoorPass/OutdoorScenery/
BuildingShells/LiveDynamic partition. EntityMatchesSet / IsShellScopedSet collapse
to the All-path constants; the set: parameter is retained as a seam for the
unified pass.

Note: the depth-clear-if-inside default-path workaround was removed per the
U.1 task list — any current indoor-wall degradation persists until a later
Phase U task lands the unified pass (expected, not a regression introduced here).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-30 16:05:19 +02:00
parent 0f7b395be1
commit 3fc77be5de
8 changed files with 37 additions and 2612 deletions

View file

@ -62,42 +62,21 @@ namespace AcDream.App.Rendering.Wb;
public sealed unsafe class WbDrawDispatcher : IDisposable
{
/// <summary>
/// Phase A8 — which subset of entities to walk in a single Draw call.
/// Used to split the indoor-cell visibility pipeline into three passes
/// when the camera is inside an EnvCell.
/// Which subset of entities to walk in a single Draw call.
///
/// Taxonomy reference: docs/research/2026-05-26-a8-entity-taxonomy.md.
/// Phase U.1 (2026-05-30): the indoor/outdoor two-pipe split (IndoorPass /
/// OutdoorScenery / BuildingShells / LiveDynamic) was deleted along with the
/// inside-out render machinery. <see cref="All"/> is the sole remaining
/// member; the unified retail-faithful pass (Phase U) draws every entity in
/// one path. The <c>set:</c> parameter is retained on the Draw overloads so
/// the unified pass can re-introduce partitioning later without re-threading
/// the call sites.
/// </summary>
public enum EntitySet
{
/// <summary>Pre-A8 behavior: every entity walked, gated only by
/// the existing <c>ParentCellId ∈ visibleCellIds</c> filter.
/// Used when the camera is OUTSIDE any EnvCell.</summary>
/// <summary>Every entity walked, gated only by the existing
/// <c>ParentCellId ∈ visibleCellIds</c> filter.</summary>
All,
/// <summary>Cell mesh + cell statics (<see cref="WorldEntity.ParentCellId"/>
/// non-null) PLUS building shell stabs (<see cref="WorldEntity.IsBuildingShell"/>
/// true) whose <see cref="WorldEntity.BuildingShellAnchorCellId"/>
/// belongs to the active building cell set. Live-dynamic
/// (<c>ServerGuid != 0</c>) is excluded; it flows through
/// <see cref="LiveDynamic"/>.</summary>
IndoorPass,
/// <summary>Outdoor/top-level stabs (<c>ParentCellId == null</c>),
/// including building shells. Drawn stencil-gated to portal
/// silhouettes when the camera is inside. Live-dynamic excluded.</summary>
OutdoorScenery,
/// <summary>Top-level building shell stabs only, optionally scoped by
/// <see cref="WorldEntity.BuildingShellAnchorCellId"/>. Used for
/// portal depth repair without walking the full outdoor scenery set.</summary>
BuildingShells,
/// <summary>Server-spawned dynamic entities (<c>ServerGuid != 0</c>):
/// player, NPCs, monsters, dropped items, animated and idle doors.
/// Drawn last with stencil disabled so they're depth-tested against
/// everything else but not stencil-clipped.</summary>
LiveDynamic,
}
private readonly GL _gl;
@ -576,13 +555,6 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
probeState,
set);
if (set == EntitySet.IndoorPass && RenderingDiagnostics.ProbeVisibilityEnabled)
{
Console.WriteLine(
$"[indoor-shells] anchorPass={walkResult.BuildingShellAnchorPass} " +
$"anchorReject={walkResult.BuildingShellAnchorReject} walked={walkResult.EntitiesWalked}");
}
// Tier 1 cache (#53) flush-tracking locals. _walkScratch holds one tuple
// per (entity, MeshRefIndex) and is in entity-order, so all MeshRefs of
// a given entity are contiguous. We accumulate ALL of an entity's
@ -1507,82 +1479,12 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
}
/// <summary>
/// Phase A8 — entity-taxonomy-aware membership test for the three-way
/// EntitySet partition. See <see cref="EntitySet"/> for the doctrine.
/// Entity-set membership test. Phase U.1 (2026-05-30): with the
/// two-pipe partition deleted, the sole <see cref="EntitySet.All"/>
/// member matches every entity. Retained as a seam for the unified
/// pass to re-introduce partitioning.
/// </summary>
private static bool EntityMatchesSet(WorldEntity entity, EntitySet set)
{
if (set == EntitySet.All) return true;
bool isLiveDynamic = entity.ServerGuid != 0;
if (set == EntitySet.LiveDynamic) return isLiveDynamic;
if (isLiveDynamic) return false; // IndoorPass/OutdoorScenery exclude live-dynamic
bool isIndoor = entity.ParentCellId.HasValue || entity.IsBuildingShell;
if (set == EntitySet.IndoorPass) return isIndoor;
if (set == EntitySet.OutdoorScenery) return !entity.ParentCellId.HasValue;
if (set == EntitySet.BuildingShells) return entity.IsBuildingShell;
throw new InvalidOperationException($"Unhandled EntitySet value: {set}");
}
/// <summary>
/// Phase A8 test helper: runs the EntitySet partition + visibleCellIds
/// gate against an in-memory entity list, returning the IDs that
/// survive both filters. Exists so the partition logic is unit-testable
/// without requiring a GL context or landblock-entries machinery.
/// </summary>
public static List<uint> WalkEntitiesForTest(
IReadOnlyList<AcDream.Core.World.WorldEntity> entities,
HashSet<uint>? visibleCellIds,
EntitySet set)
{
var output = new List<uint>();
foreach (var entity in entities)
{
if (!EntityMatchesSet(entity, set)) continue;
if (entity.MeshRefs.Count == 0) continue;
if (!EntityPassesVisibleCellGate(entity, visibleCellIds, set)) continue;
output.Add(entity.Id);
}
return output;
}
/// <summary>
/// Phase A8 RR5 (2026-05-26): pure-data walk for the explicit cellIds
/// overload. Used by RR7's IndoorPass to render only the camera-buildings'
/// cells (instead of the visibility-derived set).
///
/// <para>Indoor entities (ParentCellId set) gated by membership in
/// <paramref name="cellIds"/>. Building shells are gated by
/// BuildingShellAnchorCellId membership in the same cell set. Outdoor
/// scenery is excluded by the EntitySet partition (no cell-list gate
/// needed — EntityMatchesSet handles it).</para>
/// </summary>
public static List<uint> WalkEntitiesForTestByCellIds(
IEnumerable<AcDream.Core.World.WorldEntity> entities,
IReadOnlyCollection<uint> cellIds,
EntitySet set)
{
var result = new List<uint>();
foreach (var entity in entities)
{
if (!EntityMatchesSet(entity, set)) continue;
if (entity.MeshRefs.Count == 0) continue;
if (entity.ParentCellId.HasValue && !cellIds.Contains(entity.ParentCellId.Value))
continue;
if (IsShellScopedSet(set) && entity.IsBuildingShell)
{
if (entity.BuildingShellAnchorCellId is not uint anchorCellId ||
!cellIds.Contains(anchorCellId))
continue;
}
result.Add(entity.Id);
}
return result;
}
private static bool EntityMatchesSet(WorldEntity entity, EntitySet set) => true;
private static bool EntityPassesVisibleCellGate(
WorldEntity entity,
@ -1604,8 +1506,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
return true;
}
private static bool IsShellScopedSet(EntitySet set) =>
set == EntitySet.IndoorPass || set == EntitySet.BuildingShells;
// Phase U.1 (2026-05-30): the shell-scoped sets (IndoorPass / BuildingShells)
// were deleted with the two-pipe machinery. EntitySet.All is never shell-scoped.
private static bool IsShellScopedSet(EntitySet set) => false;
public void Dispose()
{