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:
parent
91463db551
commit
9fab1feb46
7 changed files with 57 additions and 9 deletions
|
|
@ -429,6 +429,20 @@ updates, and active animated-static synchronization. The capped, uncapped, and
|
||||||
dense-town routes are all rerun from the corrective commit; no F5 success is
|
dense-town routes are all rerun from the corrective commit; no F5 success is
|
||||||
claimed from the rejected `056bbd4e` route.
|
claimed from the rejected `056bbd4e` route.
|
||||||
|
|
||||||
|
The next exact capped route on `91463db5` completed all nine stops and shut
|
||||||
|
down gracefully, but F5 again rejected it. A transition frame reached the
|
||||||
|
post-render referee with 5,111 pending deltas because the only ordinary drain
|
||||||
|
was embedded in the spatial reconciler, which is intentionally skipped while
|
||||||
|
the world is quiesced. Later revisit frames also diverged, but the bounded
|
||||||
|
first-mismatch recorder correctly retained the earlier journal-ordering fault.
|
||||||
|
The correction makes scene publication an explicit final update-frame commit
|
||||||
|
phase after streaming, network, teleport, camera, and every conditional
|
||||||
|
spatial reconcile. It runs even while world simulation is quiesced. The former
|
||||||
|
reconciler drain is removed, leaving one ordinary update/render-boundary
|
||||||
|
publication; session reset retains its separate ordered teardown drain. The
|
||||||
|
next capped redrive must prove both zero pending deltas and expose or clear any
|
||||||
|
remaining projection mismatch before uncapped evidence begins.
|
||||||
|
|
||||||
## 6. Slice G — Frame product and production cutover
|
## 6. Slice G — Frame product and production cutover
|
||||||
|
|
||||||
### G0 — Borrowed double-buffered frame product
|
### G0 — Borrowed double-buffered frame product
|
||||||
|
|
|
||||||
|
|
@ -532,6 +532,7 @@ internal sealed class FrameRootCompositionPhase
|
||||||
session.LocalTeleport,
|
session.LocalTeleport,
|
||||||
new PlayerModeAutoEntryFramePhase(session.PlayerModeAutoEntry),
|
new PlayerModeAutoEntryFramePhase(session.PlayerModeAutoEntry),
|
||||||
cameraFrame,
|
cameraFrame,
|
||||||
|
new RenderSceneUpdateCommitPhase(live.RenderSceneShadow),
|
||||||
live.WorldAvailability);
|
live.WorldAvailability);
|
||||||
Fault(FrameRootCompositionPoint.UpdateRootCreated);
|
Fault(FrameRootCompositionPoint.UpdateRootCreated);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -606,8 +606,7 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
live.EquippedChildren,
|
live.EquippedChildren,
|
||||||
content.ParticleSink,
|
content.ParticleSink,
|
||||||
live.Lights,
|
live.Lights,
|
||||||
live.RenderSceneShadow?.LiveProjections,
|
live.RenderSceneShadow?.LiveProjections);
|
||||||
live.RenderSceneShadow);
|
|
||||||
var localPlayerAnimation = new LocalPlayerAnimationController(
|
var localPlayerAnimation = new LocalPlayerAnimationController(
|
||||||
live.LiveEntities,
|
live.LiveEntities,
|
||||||
d.PlayerIdentity,
|
d.PlayerIdentity,
|
||||||
|
|
|
||||||
|
|
@ -238,15 +238,13 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
|
||||||
private readonly ParticleHookSink _particleSink;
|
private readonly ParticleHookSink _particleSink;
|
||||||
private readonly LiveEntityLightController _lights;
|
private readonly LiveEntityLightController _lights;
|
||||||
private readonly ILiveRenderProjectionSink? _renderProjections;
|
private readonly ILiveRenderProjectionSink? _renderProjections;
|
||||||
private readonly RenderSceneShadowRuntime? _renderSceneShadow;
|
|
||||||
|
|
||||||
public LiveSpatialPresentationReconciler(
|
public LiveSpatialPresentationReconciler(
|
||||||
EntityEffectController entityEffects,
|
EntityEffectController entityEffects,
|
||||||
EquippedChildRenderController equippedChildren,
|
EquippedChildRenderController equippedChildren,
|
||||||
ParticleHookSink particleSink,
|
ParticleHookSink particleSink,
|
||||||
LiveEntityLightController lights,
|
LiveEntityLightController lights,
|
||||||
ILiveRenderProjectionSink? renderProjections = null,
|
ILiveRenderProjectionSink? renderProjections = null)
|
||||||
RenderSceneShadowRuntime? renderSceneShadow = null)
|
|
||||||
{
|
{
|
||||||
_entityEffects = entityEffects ?? throw new ArgumentNullException(nameof(entityEffects));
|
_entityEffects = entityEffects ?? throw new ArgumentNullException(nameof(entityEffects));
|
||||||
_equippedChildren = equippedChildren
|
_equippedChildren = equippedChildren
|
||||||
|
|
@ -254,7 +252,6 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
|
||||||
_particleSink = particleSink ?? throw new ArgumentNullException(nameof(particleSink));
|
_particleSink = particleSink ?? throw new ArgumentNullException(nameof(particleSink));
|
||||||
_lights = lights ?? throw new ArgumentNullException(nameof(lights));
|
_lights = lights ?? throw new ArgumentNullException(nameof(lights));
|
||||||
_renderProjections = renderProjections;
|
_renderProjections = renderProjections;
|
||||||
_renderSceneShadow = renderSceneShadow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reconcile()
|
public void Reconcile()
|
||||||
|
|
@ -264,6 +261,5 @@ internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcileP
|
||||||
_renderProjections?.SynchronizeActiveSources();
|
_renderProjections?.SynchronizeActiveSources();
|
||||||
_particleSink.RefreshAttachedEmitters();
|
_particleSink.RefreshAttachedEmitters();
|
||||||
_lights.Refresh();
|
_lights.Refresh();
|
||||||
_renderSceneShadow?.DrainUpdateBoundary();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/AcDream.App/Update/RenderSceneUpdateCommitPhase.cs
Normal file
18
src/AcDream.App/Update/RenderSceneUpdateCommitPhase.cs
Normal 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();
|
||||||
|
}
|
||||||
|
|
@ -106,6 +106,11 @@ internal interface ICameraFramePhase
|
||||||
void Tick(UpdateFrameTiming timing);
|
void Tick(UpdateFrameTiming timing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal interface IUpdateFrameCommitPhase
|
||||||
|
{
|
||||||
|
void Commit();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owns the accepted acdream host-update phase graph.
|
/// Owns the accepted acdream host-update phase graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -127,6 +132,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
|
||||||
private readonly ILocalPlayerTeleportFramePhase _teleport;
|
private readonly ILocalPlayerTeleportFramePhase _teleport;
|
||||||
private readonly IPlayerModeAutoEntryFramePhase _playerModeAutoEntry;
|
private readonly IPlayerModeAutoEntryFramePhase _playerModeAutoEntry;
|
||||||
private readonly ICameraFramePhase _camera;
|
private readonly ICameraFramePhase _camera;
|
||||||
|
private readonly IUpdateFrameCommitPhase _commit;
|
||||||
private readonly IWorldGenerationAvailability _availability;
|
private readonly IWorldGenerationAvailability _availability;
|
||||||
|
|
||||||
public UpdateFrameOrchestrator(
|
public UpdateFrameOrchestrator(
|
||||||
|
|
@ -141,6 +147,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
|
||||||
ILocalPlayerTeleportFramePhase teleport,
|
ILocalPlayerTeleportFramePhase teleport,
|
||||||
IPlayerModeAutoEntryFramePhase playerModeAutoEntry,
|
IPlayerModeAutoEntryFramePhase playerModeAutoEntry,
|
||||||
ICameraFramePhase camera,
|
ICameraFramePhase camera,
|
||||||
|
IUpdateFrameCommitPhase commit,
|
||||||
IWorldGenerationAvailability? availability = null)
|
IWorldGenerationAvailability? availability = null)
|
||||||
{
|
{
|
||||||
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
|
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
|
||||||
|
|
@ -156,6 +163,7 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
|
||||||
_playerModeAutoEntry = playerModeAutoEntry
|
_playerModeAutoEntry = playerModeAutoEntry
|
||||||
?? throw new ArgumentNullException(nameof(playerModeAutoEntry));
|
?? throw new ArgumentNullException(nameof(playerModeAutoEntry));
|
||||||
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
||||||
|
_commit = commit ?? throw new ArgumentNullException(nameof(commit));
|
||||||
_availability = availability ?? AlwaysAvailableWorldGeneration.Instance;
|
_availability = availability ?? AlwaysAvailableWorldGeneration.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,5 +193,6 @@ internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdat
|
||||||
_teleport.Tick(timing.SimulationDeltaSecondsSingle);
|
_teleport.Tick(timing.SimulationDeltaSecondsSingle);
|
||||||
_playerModeAutoEntry.TryEnter();
|
_playerModeAutoEntry.TryEnter();
|
||||||
_camera.Tick(timing);
|
_camera.Tick(timing);
|
||||||
|
_commit.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
"teleport",
|
"teleport",
|
||||||
"auto-entry",
|
"auto-entry",
|
||||||
"camera",
|
"camera",
|
||||||
|
"commit",
|
||||||
],
|
],
|
||||||
calls);
|
calls);
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +66,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
"inbound-player-projection",
|
"inbound-player-projection",
|
||||||
"inbound-player-reconcile",
|
"inbound-player-reconcile",
|
||||||
"camera",
|
"camera",
|
||||||
|
"commit",
|
||||||
],
|
],
|
||||||
calls);
|
calls);
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +91,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
[
|
[
|
||||||
"teardown", "clock", "streaming", "input", "objects",
|
"teardown", "clock", "streaming", "input", "objects",
|
||||||
"network", "commands", "ordinary-reconcile", "liveness",
|
"network", "commands", "ordinary-reconcile", "liveness",
|
||||||
"teleport", "auto-entry", "camera",
|
"teleport", "auto-entry", "camera", "commit",
|
||||||
],
|
],
|
||||||
calls);
|
calls);
|
||||||
Assert.Equal([0.0], observed.PublishedTimes);
|
Assert.Equal([0.0], observed.PublishedTimes);
|
||||||
|
|
@ -153,6 +155,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
Assert.Equal(3, calls.Count(call => call == "network"));
|
Assert.Equal(3, calls.Count(call => call == "network"));
|
||||||
Assert.Equal(3, calls.Count(call => call == "teleport"));
|
Assert.Equal(3, calls.Count(call => call == "teleport"));
|
||||||
Assert.Equal(3, calls.Count(call => call == "camera"));
|
Assert.Equal(3, calls.Count(call => call == "camera"));
|
||||||
|
Assert.Equal(3, calls.Count(call => call == "commit"));
|
||||||
Assert.Equal([0.25f, 0.25f, 0.25f], observed.LiveDeltas);
|
Assert.Equal([0.25f, 0.25f, 0.25f], observed.LiveDeltas);
|
||||||
Assert.Equal([0.25f, 0.25f, 0.25f], observed.TeleportDeltas);
|
Assert.Equal([0.25f, 0.25f, 0.25f], observed.TeleportDeltas);
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +193,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
[
|
[
|
||||||
"teardown", "clock", "streaming", "input", "objects", "network",
|
"teardown", "clock", "streaming", "input", "objects", "network",
|
||||||
"commands", "ordinary-reconcile", "liveness", "teleport",
|
"commands", "ordinary-reconcile", "liveness", "teleport",
|
||||||
"auto-entry", "camera",
|
"auto-entry", "camera", "commit",
|
||||||
];
|
];
|
||||||
Assert.Equal(oneFrame.Concat(oneFrame), calls);
|
Assert.Equal(oneFrame.Concat(oneFrame), calls);
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +231,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
typeof(ILocalPlayerTeleportFramePhase),
|
typeof(ILocalPlayerTeleportFramePhase),
|
||||||
typeof(IPlayerModeAutoEntryFramePhase),
|
typeof(IPlayerModeAutoEntryFramePhase),
|
||||||
typeof(ICameraFramePhase),
|
typeof(ICameraFramePhase),
|
||||||
|
typeof(IUpdateFrameCommitPhase),
|
||||||
typeof(IWorldGenerationAvailability),
|
typeof(IWorldGenerationAvailability),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -907,6 +911,7 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
new RecordingTeleport(calls, observed, teleportPlace),
|
new RecordingTeleport(calls, observed, teleportPlace),
|
||||||
new RecordingAutoEntry(calls),
|
new RecordingAutoEntry(calls),
|
||||||
new RecordingCamera(calls, observed, inboundCreatedPlayer),
|
new RecordingCamera(calls, observed, inboundCreatedPlayer),
|
||||||
|
new RecordingCommit(calls),
|
||||||
availability);
|
availability);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1023,6 +1028,12 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class RecordingCommit(List<string> calls)
|
||||||
|
: IUpdateFrameCommitPhase
|
||||||
|
{
|
||||||
|
public void Commit() => calls.Add("commit");
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class FrameObservations
|
private sealed class FrameObservations
|
||||||
{
|
{
|
||||||
public List<double> PublishedTimes { get; } = [];
|
public List<double> PublishedTimes { get; } = [];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue