diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 1632bd02..481eb6fe 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7384,7 +7384,16 @@ public sealed class GameWindow : IDisposable bool playerIndoorGate = AcDream.Core.Rendering.RenderingDiagnostics.ShouldRenderIndoor( playerCellId, playerRoot is not null); - var clipRoot = playerIndoorGate && viewerRoot is not null ? viewerRoot : null; + // Render unification (outdoor-as-cell, 2026-06-07 cutover): ONE render path rooted at the + // VIEWER cell. Eye indoors -> its interior EnvCell (viewerRoot); eye outdoors -> the + // synthetic outdoor node (_outdoorNode, built above from nearby building entrances). The + // result is null ONLY when neither exists (pre-spawn / login / legacy non-chase camera) -> + // the outdoor LScape block below still runs as the safety path (and login still shows the + // live sky). There is no inside/outside branch to TOGGLE as the chase eye crosses the + // doorway boundary, so the indoor FLAP dies by construction. playerIndoorGate stays + // computed for the [render-sig] probe but no longer selects the path (handoff + // docs/research/2026-06-07-render-unification-cutover-flip-handoff.md section 4 Step B). + var clipRoot = viewerRoot ?? _outdoorNode; string renderBranch = clipRoot is null ? "OutdoorRoot" : "RetailPViewInside"; @@ -7553,6 +7562,23 @@ public sealed class GameWindow : IDisposable : sigSceneParticles; sigOutdoorSceneryDrawn = pviewResult.Partition.Outdoor.Count > 0 && pviewResult.ClipAssembly.OutsideViewSlices.Length > 0; + + // Render unification: DrawInside draws the Outdoor bucket (through the landscape + // slice) and the per-cell ByCell buckets, but NOT LiveDynamic — server entities with + // no resolved ParentCellId (the transient just-spawned / unpositioned case the old + // outdoor branch drew at the bottom of its block). Preserve that draw for the + // outdoor-node root so no live entity blinks out outdoors (spec section 10 regression + // guard). DrawInside's tail clears entity clip routing and disables clip distances, so + // visibleCellIds:null draws them unclipped — identical to the old outdoor path. + if (ReferenceEquals(clipRoot, _outdoorNode) + && _interiorRenderer is not null + && pviewResult.Partition.LiveDynamic.Count > 0) + { + _interiorRenderer.DrawEntityBucket( + camera, frustum, playerLb, animatedIds, + pviewResult.Partition.LiveDynamic, visibleCellIds: null); + sigLiveDynamicDrawnCount = pviewResult.Partition.LiveDynamic.Count; + } } else {