diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index bd54b28..5869071 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -36,7 +36,6 @@ public sealed class GameWindow : IDisposable
private AcDream.App.Rendering.Wb.EntitySpawnAdapter? _wbEntitySpawnAdapter;
private AcDream.App.Rendering.Vfx.EntityScriptActivator? _entityScriptActivator;
private AcDream.App.Rendering.Wb.WbDrawDispatcher? _wbDrawDispatcher;
- private IndoorCellStencilPipeline? _indoorStencilPipeline;
/// Phase N.5: ARB_bindless_texture + ARB_shader_draw_parameters
/// support. Required at startup — missing bindless throws
/// in OnLoad.
@@ -1753,15 +1752,6 @@ public sealed class GameWindow : IDisposable
_classificationCache);
// A.5 T22.5: apply A2C gate from quality preset.
_wbDrawDispatcher.AlphaToCoverage = _resolvedQuality.AlphaToCoverage;
-
- // Phase A8 R3 — indoor visibility culling pipeline. Owns the
- // portal_stencil shader pair and a dynamic VBO for per-frame
- // portal triangle uploads. Stencil work runs only when the
- // camera is inside an EnvCell; outside, the object is dormant.
- _indoorStencilPipeline = new IndoorCellStencilPipeline(
- _gl,
- Path.Combine(shadersDir, "portal_stencil.vert"),
- Path.Combine(shadersDir, "portal_stencil.frag"));
}
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
@@ -7167,10 +7157,14 @@ public sealed class GameWindow : IDisposable
if (cameraInsideCell)
_gl!.Clear(ClearBufferMask.DepthBufferBit);
- // L-fix1 (2026-04-28): animated-entity id set. Required by both
- // the cameraInsideCell branch (to route them to LiveDynamic pass)
- // and the outdoor path (where it preserves visibility across
- // landblock frustum culling).
+ // L-fix1 (2026-04-28): pass the set of animated-entity ids so
+ // the renderer keeps remote players / NPCs / monsters
+ // visible even when their landblock rotates out of the
+ // frustum. Without this, other characters wink in/out as
+ // the camera turns. The set is rebuilt per-frame from
+ // _animatedEntities — it's small (<100 entities typically)
+ // so HashSet allocation is cheap. Static scenery still
+ // respects landblock-level cull.
HashSet? animatedIds = null;
if (_animatedEntities.Count > 0)
{
@@ -7179,79 +7173,11 @@ public sealed class GameWindow : IDisposable
animatedIds.Add(k);
}
- if (cameraInsideCell && _indoorStencilPipeline is not null
- && visibility?.CameraCell is not null)
- {
- if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeVisibilityEnabled)
- Console.WriteLine("[vis] branch=indoor");
- // Phase A8 R3 — WB RenderInsideOut order.
- //
- // 1. Terrain has already drawn (color + depth) at line ~7104.
- // 2. depth-clear-if-inside has already cleared depth to 1.0
- // (above this branch at line ~7118). The MarkAndPunch
- // below is a no-op against that baseline — left in for
- // symmetry with WB's reference pipeline and to handle
- // the unusual case where depth-clear is later dropped.
- // 3. MarkAndPunch — stencil bit 1 at the camera's-own-cell
- // exit portals. Step 5 (cross-cell-portal visibility via
- // 3-stencil-bit pipeline) is DEFERRED — we mark ONLY the
- // camera's own cell's portals, not the BFS-extended
- // VisibleCellIds. Trade-off: cross-cell visibility loss
- // (rare visually); correctness in the common case (no
- // see-through-wall to far-side portal openings).
- var cameraCells = new[] { visibility.CameraCell };
- _indoorStencilPipeline.UploadPortalMesh(cameraCells);
-
- var viewProjection = camera.View * camera.Projection;
- _indoorStencilPipeline.MarkAndPunch(viewProjection);
-
- // 4. IndoorPass — cell mesh + cell statics + building shells
- // (R1's IsBuildingShell flag drives the partition).
- // Stencil OFF (MarkAndPunch's cleanup restored that).
- // Depth test normal; building shells write the wall depth
- // that protects the indoor from outdoor visibility.
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.IndoorPass);
-
- // 5. Stencil-gated outdoor pass.
- _indoorStencilPipeline.EnableOutdoorPass();
-
- // 5a. Re-draw terrain — at portal-silhouette pixels only,
- // terrain Z (with the f48c74a -0.01 nudge) wins over the
- // punched 1.0 depth. Color writes through window.
- _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
-
- // 5b. Outdoor scenery — same stencil gating.
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery);
-
- // 6. Stencil OFF — live dynamic entities draw freely with
- // depth test only (no stencil clipping).
- _indoorStencilPipeline.DisableStencil();
-
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.LiveDynamic);
- }
- else
- {
- if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeVisibilityEnabled)
- Console.WriteLine("[vis] branch=outdoor");
- // Outdoor path — unchanged from pre-A8: single dispatcher call
- // walks every entity with default EntitySet.All partition.
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility?.VisibleCellIds,
- animatedEntityIds: animatedIds);
- }
+ // N.5: WbDrawDispatcher is always non-null (modern path mandatory).
+ _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
+ neverCullLandblockId: playerLb,
+ visibleCellIds: visibility?.VisibleCellIds,
+ animatedEntityIds: animatedIds);
// Phase G.1 / E.3: draw all live particles after opaque
// scene geometry so alpha blending composites correctly.
@@ -10607,7 +10533,6 @@ public sealed class GameWindow : IDisposable
_liveSession = null;
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
_wbDrawDispatcher?.Dispose();
- _indoorStencilPipeline?.Dispose();
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
_samplerCache?.Dispose();
_textureCache?.Dispose();