fix(rendering): publish scene deltas at update boundary

Move the ordinary shadow-scene drain out of the conditional spatial reconciler and into an explicit final update-frame commit phase. This publishes deltas after streaming, network, teleport, camera, and conditional reconciles even while world simulation is quiesced.

Release gate: 3,733 App tests / 3 skips; 8,217 complete-solution tests / 5 skips.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-24 23:00:31 +02:00
parent 91463db551
commit 9fab1feb46
7 changed files with 57 additions and 9 deletions

View file

@ -532,6 +532,7 @@ internal sealed class FrameRootCompositionPhase
session.LocalTeleport,
new PlayerModeAutoEntryFramePhase(session.PlayerModeAutoEntry),
cameraFrame,
new RenderSceneUpdateCommitPhase(live.RenderSceneShadow),
live.WorldAvailability);
Fault(FrameRootCompositionPoint.UpdateRootCreated);

View file

@ -606,8 +606,7 @@ internal sealed class SessionPlayerCompositionPhase
live.EquippedChildren,
content.ParticleSink,
live.Lights,
live.RenderSceneShadow?.LiveProjections,
live.RenderSceneShadow);
live.RenderSceneShadow?.LiveProjections);
var localPlayerAnimation = new LocalPlayerAnimationController(
live.LiveEntities,
d.PlayerIdentity,

View file

@ -238,15 +238,13 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
private readonly ParticleHookSink _particleSink;
private readonly LiveEntityLightController _lights;
private readonly ILiveRenderProjectionSink? _renderProjections;
private readonly RenderSceneShadowRuntime? _renderSceneShadow;
public LiveSpatialPresentationReconciler(
EntityEffectController entityEffects,
EquippedChildRenderController equippedChildren,
ParticleHookSink particleSink,
LiveEntityLightController lights,
ILiveRenderProjectionSink? renderProjections = null,
RenderSceneShadowRuntime? renderSceneShadow = null)
ILiveRenderProjectionSink? renderProjections = null)
{
_entityEffects = entityEffects ?? throw new ArgumentNullException(nameof(entityEffects));
_equippedChildren = equippedChildren
@ -254,7 +252,6 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
_particleSink = particleSink ?? throw new ArgumentNullException(nameof(particleSink));
_lights = lights ?? throw new ArgumentNullException(nameof(lights));
_renderProjections = renderProjections;
_renderSceneShadow = renderSceneShadow;
}
public void Reconcile()
@ -264,6 +261,5 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
_renderProjections?.SynchronizeActiveSources();
_particleSink.RefreshAttachedEmitters();
_lights.Refresh();
_renderSceneShadow?.DrainUpdateBoundary();
}
}

View file

@ -0,0 +1,18 @@
using AcDream.App.Rendering.Scene;
namespace AcDream.App.Update;
/// <summary>
/// Publishes every render-projection delta at the final update/render
/// boundary, after streaming, network, teleport, and camera-owned spatial
/// mutations have completed.
/// </summary>
internal sealed class RenderSceneUpdateCommitPhase : IUpdateFrameCommitPhase
{
private readonly RenderSceneShadowRuntime? _shadow;
public RenderSceneUpdateCommitPhase(RenderSceneShadowRuntime? shadow) =>
_shadow = shadow;
public void Commit() => _shadow?.DrainUpdateBoundary();
}

View file

@ -106,6 +106,11 @@ internal interface ICameraFramePhase
void Tick(UpdateFrameTiming timing);
}
internal interface IUpdateFrameCommitPhase
{
void Commit();
}
/// <summary>
/// Owns the accepted acdream host-update phase graph.
/// </summary>
@ -127,6 +132,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
private readonly ILocalPlayerTeleportFramePhase _teleport;
private readonly IPlayerModeAutoEntryFramePhase _playerModeAutoEntry;
private readonly ICameraFramePhase _camera;
private readonly IUpdateFrameCommitPhase _commit;
private readonly IWorldGenerationAvailability _availability;
public UpdateFrameOrchestrator(
@ -141,6 +147,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
ILocalPlayerTeleportFramePhase teleport,
IPlayerModeAutoEntryFramePhase playerModeAutoEntry,
ICameraFramePhase camera,
IUpdateFrameCommitPhase commit,
IWorldGenerationAvailability? availability = null)
{
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
@ -156,6 +163,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
_playerModeAutoEntry = playerModeAutoEntry
?? throw new ArgumentNullException(nameof(playerModeAutoEntry));
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
_commit = commit ?? throw new ArgumentNullException(nameof(commit));
_availability = availability ?? AlwaysAvailableWorldGeneration.Instance;
}
@ -185,5 +193,6 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
_teleport.Tick(timing.SimulationDeltaSecondsSingle);
_playerModeAutoEntry.TryEnter();
_camera.Tick(timing);
_commit.Commit();
}
}