acdream/src/AcDream.App/Rendering/WorldSceneRenderer.cs
Erik fdc4fd496d fix(ui): gate — no void frames around the login wormhole; vitals icons centered
The login tunnel now covers from the first world-facing frame (the
sky-void backdrop can never present pre-tunnel) and holds through an
atomic tunnel-to-world swap at reveal completion — the void is
structurally unreachable on both edges, pinned by frame-sequence tests
across WorldSceneRenderer/WorldRevealCoordinator/LocalPlayerTeleport-
Controller/RuntimeWorldTransitState. Vitals detail icons draw at their
authored centered offsets in both stacked and side-by-side layouts.
Implemented and live-probed by the fix agent; finalized by the lead
after the agent parked post-verification (gates re-run green:
App 5512/3, Runtime 1747/0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 12:30:51 +02:00

352 lines
14 KiB
C#

using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Rendering;
namespace AcDream.App.Rendering;
internal interface IWorldScenePViewRenderer
{
IReadOnlySet<uint> OutdoorSceneParticleEntityIds { get; }
RetailPViewFrameResult DrawInside(RetailPViewFrameInput input);
void AbortFrame();
}
internal sealed class WorldScenePViewRenderer : IWorldScenePViewRenderer
{
private readonly RetailPViewRenderer _renderer;
private readonly IRetailPViewPassExecutor _passes;
private readonly IOutdoorSceneParticleOwnerSource _particles;
public WorldScenePViewRenderer(
RetailPViewRenderer renderer,
IRetailPViewPassExecutor passes,
IOutdoorSceneParticleOwnerSource particles)
{
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
_passes = passes ?? throw new ArgumentNullException(nameof(passes));
_particles = particles ?? throw new ArgumentNullException(nameof(particles));
}
public IReadOnlySet<uint> OutdoorSceneParticleEntityIds =>
_particles.OutdoorSceneParticleEntityIds;
public RetailPViewFrameResult DrawInside(RetailPViewFrameInput input) =>
_renderer.DrawInside(input, _passes);
public void AbortFrame() => _passes.AbortFrame();
}
/// <summary>
/// Owns the normal-world render transaction between shared frame-resource
/// preparation and private presentation. It preserves retail's one PView
/// decision: a resolved viewer root uses DrawInside; only an unresolved root
/// uses the flat safety path.
/// </summary>
internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
{
private readonly IRenderFrameFoundationSource _foundation;
private readonly IRenderLoginStateSource _login;
private readonly IWorldSceneSkyStateSource _sky;
private readonly IWorldRenderFrameBuilder _frames;
private readonly IWorldSceneEntitySource _entities;
private readonly IWorldSceneSelectionFrame? _selection;
private readonly IWorldSceneAlphaFrame _alpha;
private readonly IWorldSceneParticleVisibility _particleVisibility;
private readonly IWorldScenePViewRenderer _pview;
private readonly IRetailPViewCellSource _pviewCells;
private readonly IWorldScenePassExecutor _passes;
private readonly IWorldRenderRangeSource _renderRange;
private readonly IWorldSceneDiagnostics _diagnostics;
private readonly IWorldGenerationAvailability _availability;
private readonly RetailPViewFrameInput _pviewFrameInput = new();
public WorldSceneRenderer(
IRenderFrameFoundationSource foundation,
IRenderLoginStateSource login,
IWorldSceneSkyStateSource sky,
IWorldRenderFrameBuilder frames,
IWorldSceneEntitySource entities,
IWorldSceneSelectionFrame? selection,
IWorldSceneAlphaFrame alpha,
IWorldSceneParticleVisibility particleVisibility,
IWorldScenePViewRenderer pview,
IRetailPViewCellSource pviewCells,
IWorldScenePassExecutor passes,
IWorldRenderRangeSource renderRange,
IWorldSceneDiagnostics diagnostics,
IWorldGenerationAvailability? availability = null)
{
_foundation = foundation ?? throw new ArgumentNullException(nameof(foundation));
_login = login ?? throw new ArgumentNullException(nameof(login));
_sky = sky ?? throw new ArgumentNullException(nameof(sky));
_frames = frames ?? throw new ArgumentNullException(nameof(frames));
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
_selection = selection;
_alpha = alpha ?? throw new ArgumentNullException(nameof(alpha));
_particleVisibility = particleVisibility
?? throw new ArgumentNullException(nameof(particleVisibility));
_pview = pview ?? throw new ArgumentNullException(nameof(pview));
_pviewCells = pviewCells ?? throw new ArgumentNullException(nameof(pviewCells));
_passes = passes ?? throw new ArgumentNullException(nameof(passes));
_renderRange = renderRange ?? throw new ArgumentNullException(nameof(renderRange));
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
_availability = availability ?? AlwaysAvailableWorldGeneration.Instance;
}
public WorldRenderFrameOutcome Render(RenderFrameInput input)
{
_ = input;
bool selectionFrameStarted = _selection is not null;
bool worldFrameStarted = false;
bool pviewFrameStarted = false;
try
{
_selection?.BeginFrame();
if (!_availability.IsWorldAvailable)
{
_selection?.CompleteFrame();
selectionFrameStarted = false;
return default;
}
RenderFrameFoundation foundation = _foundation.Foundation;
if (foundation.PortalViewportVisible)
{
_selection?.CompleteFrame();
selectionFrameStarted = false;
return default;
}
// Set before BeginFrame so an already-poisoned queue is recovered by
// the same abort path instead of making every later frame fail.
worldFrameStarted = true;
_alpha.BeginFrame();
WorldRenderFrame world = _frames.Build(
in foundation,
_login.IsWaitingForLogin,
_sky.ActiveDayGroup);
_passes.BeginFrame();
WorldCameraFrame camera = world.Camera;
WorldRootFrame roots = world.Roots;
LoadedCell? clipRoot = world.ClipRoot;
bool renderSky = roots.RenderSky;
bool drawSkyThisFrame = false;
bool terrainDrawn = false;
bool skyDrawn = false;
bool depthClear = false;
bool outdoorSceneryDrawn = false;
int liveDynamicDrawnCount = 0;
string sceneParticles = "none";
TerrainClipMode terrainClipMode = TerrainClipMode.Planes;
RetailPViewFrameResult? pviewResult = null;
if (clipRoot is null)
{
_passes.PrepareFlatWorldClip();
drawSkyThisFrame = renderSky;
skyDrawn = drawSkyThisFrame;
if (drawSkyThisFrame)
{
_passes.DrawFlatSky(
in camera,
in foundation,
_sky.ActiveDayGroup,
_sky.DayFraction);
}
// The former sky-only "waiting for login" skip lived here.
// It is unreachable now: LocalPlayerTeleportRenderStateSource
// folds IsWaitingForLogin into PortalViewportVisible (retail's
// pre-player gameplay screen draws NO world — black, not sky),
// so the portal-visible return above already covers every
// waiting frame. One gate computes the frame's visibility;
// this phase only enforces it.
_passes.DrawFlatTerrain(in camera, roots.PlayerLandblockId);
terrainDrawn = true;
}
else
{
terrainClipMode = TerrainClipMode.Skip;
}
if (clipRoot is not null)
{
pviewFrameStarted = true;
pviewResult = _pview.DrawInside(
_pviewFrameInput.Reset(
clipRoot,
world.Buildings.NearbyBuildingCells,
roots.ViewerEyePosition,
camera.ViewProjection,
_pviewCells,
camera.Camera,
camera.Position,
camera.Frustum,
roots.PlayerLandblockId,
world.AnimatedEntityIds,
roots.RenderCenterLandblockX,
roots.RenderCenterLandblockY,
_renderRange.NearRadius,
_entities.LandblockEntries,
renderSky,
roots.PlayerSeenOutside,
_sky.DayFraction,
_sky.ActiveDayGroup,
foundation.Sky,
foundation.EnvironOverrideActive,
roots.ViewerCellId,
roots.PlayerCellId,
roots.PlayerViewPosition,
camera.Camera.View,
_diagnostics.CameraCellResolution));
_particleVisibility.MarkVisibleCells(pviewResult.DrawableCells);
_frames.ObserveDrawableCells(pviewResult.DrawableCells);
_diagnostics.EmitPViewInput(
pviewResult.PortalFrame,
camera.ViewProjection,
clipRoot,
camera.Position,
roots.PlayerViewPosition);
bool hasOutsideSlice = pviewResult.ClipAssembly.OutsideViewSlices.Length > 0;
terrainDrawn = hasOutsideSlice;
skyDrawn = renderSky && hasOutsideSlice;
// This mirrors the established diagnostic meaning, including
// outdoor roots whose PView executor does not issue an interior
// depth clear.
depthClear = hasOutsideSlice;
RenderProjectionCounts sourceCounts =
pviewResult.SourceCounts;
if (sourceCounts.IndoorCellStatic > 0 || hasOutsideSlice)
sceneParticles = "pviewScoped";
outdoorSceneryDrawn =
sourceCounts.OutdoorStatic > 0
&& hasOutsideSlice;
liveDynamicDrawnCount =
sourceCounts.LiveDynamicRoot;
}
else
{
_passes.DrawFlatEntities(
in camera,
_entities.LandblockEntries,
roots.PlayerLandblockId,
world.AnimatedEntityIds);
_frames.ClearDrawableCells();
}
_passes.DisableClipDistances();
sceneParticles = _passes.DrawPostWorldParticles(
clipRoot,
pviewResult?.ClipAssembly,
in camera,
_pview.OutdoorSceneParticleEntityIds,
sceneParticles);
if (clipRoot is null && drawSkyThisFrame)
{
skyDrawn = true;
_passes.DrawFlatWeather(
in camera,
in foundation,
_sky.ActiveDayGroup,
_sky.DayFraction);
}
_diagnostics.EmitRenderSignature(
clipRoot is null ? "OutdoorRoot" : "RetailPViewInside",
clipRoot,
roots.ViewerRoot,
roots.PlayerRoot,
roots.ViewerCellId,
roots.PlayerCellId,
roots.PlayerIndoorGate,
roots.CameraInsideCell,
renderSky,
drawSkyThisFrame,
terrainDrawn,
terrainClipMode,
skyDrawn,
depthClear,
outdoorSceneryDrawn,
liveDynamicDrawnCount,
sceneParticles,
pviewResult,
camera.Position,
roots.PlayerViewPosition);
WorldSceneDiagnosticOutcome diagnostic = _diagnostics.DrawAndPublish(
in camera,
_entities.LandblockBounds);
CompleteWorldFrame();
worldFrameStarted = false;
pviewFrameStarted = false;
_selection?.CompleteFrame();
selectionFrameStarted = false;
return new WorldRenderFrameOutcome(
diagnostic.VisibleLandblocks,
diagnostic.TotalLandblocks,
NormalWorldDrawn: true);
}
catch (Exception renderFailure)
{
List<Exception>? abortFailures = AbortFrame(
worldFrameStarted,
pviewFrameStarted,
selectionFrameStarted);
if (abortFailures is { Count: > 0 })
{
abortFailures.Insert(0, renderFailure);
throw new AggregateException(
"World rendering failed and the incomplete frame could not be fully aborted.",
abortFailures);
}
throw;
}
}
private void CompleteWorldFrame()
{
_alpha.EndFrame();
if (_passes.TerrainVisibleCellIds is { } visibleTerrainCells)
_particleVisibility.MarkVisibleCells(visibleTerrainCells);
_particleVisibility.CompleteFrame();
}
private List<Exception>? AbortFrame(
bool worldFrameStarted,
bool pviewFrameStarted,
bool selectionFrameStarted)
{
List<Exception>? failures = null;
if (worldFrameStarted)
{
if (pviewFrameStarted)
TryAbort(_pview.AbortFrame);
TryAbort(_passes.AbortFrame);
TryAbort(_particleVisibility.AbortFrame);
TryAbort(_alpha.AbortFrame);
}
if (selectionFrameStarted && _selection is not null)
TryAbort(_selection.AbortFrame);
return failures;
void TryAbort(Action abort)
{
try
{
abort();
}
catch (Exception error)
{
(failures ??= []).Add(error);
}
}
}
}