diff --git a/src/AcDream.App/Rendering/CellVisibility.cs b/src/AcDream.App/Rendering/CellVisibility.cs
index ffddd9a..b4debbc 100644
--- a/src/AcDream.App/Rendering/CellVisibility.cs
+++ b/src/AcDream.App/Rendering/CellVisibility.cs
@@ -344,17 +344,6 @@ public sealed class CellVisibility
local.Z <= cell.LocalBoundsMax.Z + PointInCellEpsilon;
}
- ///
- /// Looks up a loaded cell by full 32-bit cell id, or returns null if
- /// not loaded. Used by the Phase A8 stencil pipeline to materialize
- /// back into
- /// instances for portal mesh extraction.
- ///
- public LoadedCell? TryGetCell(uint cellId)
- {
- return _cellLookup.TryGetValue(cellId, out var cell) ? cell : null;
- }
-
///
/// Brute-force scan of every loaded cell to test whether
/// is inside any of them. Does not touch
diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index 464fbb0..e1e3e1b 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -108,10 +108,6 @@ public sealed class GameWindow : IDisposable
// Step 4: portal-based interior cell visibility.
private readonly CellVisibility _cellVisibility = new();
- // Phase A8: indoor-cell stencil pipeline (#78). Null until OnLoad runs
- // (requires GL context). Never null after OnLoad completes normally.
- private IndoorCellStencilPipeline? _indoorStencil;
-
// Phase A.1 hotfix / Phase A.5 T10: DatCollection is NOT thread-safe.
// DatReaderWriter's DatBinReader uses a shared buffer position internally —
// concurrent _dats.Get calls from the streaming worker thread (T11+) and
@@ -1773,13 +1769,6 @@ public sealed class GameWindow : IDisposable
// the player.
_particleRenderer = new ParticleRenderer(_gl, shadersDir, _textureCache, _dats);
- // Phase A8 — indoor-cell visibility culling pipeline (#78).
- // Shader files are deployed alongside the existing terrain/mesh
- // shaders via the same .csproj content glob.
- _indoorStencil = new IndoorCellStencilPipeline(_gl,
- Path.Combine(shadersDir, "portal_stencil.vert"),
- Path.Combine(shadersDir, "portal_stencil.frag"));
-
// A.5 T22.5: apply radii from the already-resolved _resolvedQuality.
// _resolvedQuality was set by the quality block immediately after
// LoadAndApplyPersistedSettings() above, absorbing all env-var overrides.
@@ -7108,38 +7097,26 @@ public sealed class GameWindow : IDisposable
goto SkipWorldGeometry;
}
- // Phase A8 — indoor-cell visibility culling.
- // When the camera is inside an EnvCell, build a portal-silhouette
- // stencil mask and use it to gate outdoor passes (terrain +
- // outdoor entities) so they only write fragments inside actual
- // portal openings. WB-style stencil port (closes #78 + cellar-
- // stairs artifact). Retail oracle: PView::DrawCells at
- // acclient_2013_pseudo_c.txt:432709 ("outside_view.view_count > 0").
- int portalTriCount = 0;
- if (cameraInsideCell && visibility is not null && _indoorStencil is not null)
- {
- // Resolve VisibleCellIds → LoadedCell list via
- // CellVisibility.TryGetCell (added in Task 7).
- var currentBuildingCells = new List(visibility.VisibleCellIds.Count);
- foreach (var id in visibility.VisibleCellIds)
- {
- var cell = _cellVisibility.TryGetCell(id);
- if (cell is not null) currentBuildingCells.Add(cell);
- }
- portalTriCount = _indoorStencil.UploadPortalMesh(currentBuildingCells);
+ // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup
+ // (gated on ACDREAM_WB_DIAG=1, same env var as [WB-DIAG]). Stopwatch
+ // is cheap; only the periodic Console.WriteLine is gated.
+ _terrainCpuStopwatch.Restart();
+ _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
+ _terrainCpuStopwatch.Stop();
+ // Multiply by 100 then divide by 100 in the diag print to keep
+ // 0.01 µs precision in the long-typed sample buffer. Terrain Draw
+ // is sub-microsecond on simple scenes; truncating to integer µs
+ // would round nearly every sample to 0.
+ _terrainCpuSamples[_terrainCpuSampleCursor] = (long)(_terrainCpuStopwatch.Elapsed.TotalMicroseconds * 100.0);
+ _terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length;
+ MaybeFlushTerrainDiag();
- if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeVisibilityEnabled)
- {
- Console.WriteLine(
- $"[vis] cameraInside=true cells={visibility.VisibleCellIds.Count} " +
- $"exitPortalVisible={visibility.HasExitPortalVisible} " +
- $"portalTris={portalTriCount / 3}");
- }
- }
- else if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeVisibilityEnabled)
- {
- Console.WriteLine("[vis] cameraInside=false");
- }
+ // Conditional depth clear: when camera is inside a building, clear
+ // depth (not color) so interior geometry writes fresh Z values on top
+ // of the terrain color buffer. Exit portals show outdoor terrain color
+ // because we kept the color buffer. Matching ACME GameScene.cs pattern.
+ if (cameraInsideCell)
+ _gl!.Clear(ClearBufferMask.DepthBufferBit);
// L-fix1 (2026-04-28): pass the set of animated-entity ids so
// the renderer keeps remote players / NPCs / monsters
@@ -7157,65 +7134,11 @@ public sealed class GameWindow : IDisposable
animatedIds.Add(k);
}
- if (cameraInsideCell && portalTriCount > 0)
- {
- // WB Step 3: draw indoor entities first, stencil OFF.
- // Indoor entities (cell mesh + cell statics) ALWAYS draw
- // inside the camera-building, regardless of portal coverage.
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility!.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.IndoorOnly);
-
- // WB Steps 1+2: mark stencil + punch far depth in portal regions.
- _indoorStencil!.MarkAndPunch(camera.View * camera.Projection);
-
- // WB Step 4a: terrain, stencil-gated to portal silhouettes.
- _indoorStencil!.EnableOutdoorPass();
- // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup
- // (gated on ACDREAM_WB_DIAG=1, same env var as [WB-DIAG]). Stopwatch
- // is cheap; only the periodic Console.WriteLine is gated.
- _terrainCpuStopwatch.Restart();
- _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
- _terrainCpuStopwatch.Stop();
- _terrainCpuSamples[_terrainCpuSampleCursor] =
- (long)(_terrainCpuStopwatch.Elapsed.TotalMicroseconds * 100.0);
- _terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length;
- MaybeFlushTerrainDiag();
-
- // WB Step 4b: outdoor entities, still stencil-gated.
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorOnly);
-
- _indoorStencil!.DisableStencil();
- }
- else
- {
- // Outdoor (or indoor with no exit portals): pre-A8 path.
- // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup.
- _terrainCpuStopwatch.Restart();
- _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
- _terrainCpuStopwatch.Stop();
- // Multiply by 100 then divide by 100 in the diag print to keep
- // 0.01 µs precision in the long-typed sample buffer. Terrain Draw
- // is sub-microsecond on simple scenes; truncating to integer µs
- // would round nearly every sample to 0.
- _terrainCpuSamples[_terrainCpuSampleCursor] =
- (long)(_terrainCpuStopwatch.Elapsed.TotalMicroseconds * 100.0);
- _terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length;
- MaybeFlushTerrainDiag();
-
- // N.5: WbDrawDispatcher is always non-null (modern path mandatory).
- _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
- neverCullLandblockId: playerLb,
- visibleCellIds: visibility?.VisibleCellIds,
- animatedEntityIds: animatedIds,
- set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.All);
- }
+ // 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.
@@ -10571,8 +10494,6 @@ public sealed class GameWindow : IDisposable
_liveSession = null;
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
_wbDrawDispatcher?.Dispose();
- _indoorStencil?.Dispose();
- _indoorStencil = null;
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
_samplerCache?.Dispose();
_textureCache?.Dispose();