feat(rendering): compare the incremental shadow scene

Construct Slice F's non-drawing scene only for lifecycle automation, drain accepted static and live deltas at the final update boundary, and compare exact current-path fingerprints at cadence and checkpoints. Publish bounded mismatch, journal, index, digest, and memory evidence without changing normal launches or draw submission.

Release: 8,211 passed, 5 skipped.
This commit is contained in:
Erik 2026-07-24 22:24:30 +02:00
parent ff5d86175f
commit 0eb6648589
21 changed files with 937 additions and 20 deletions

View file

@ -374,6 +374,20 @@ internal sealed class FrameRootCompositionPhase
&& d.Options.AutomationArtifactDirectory is not null
? new CurrentRenderSceneOracle()
: null;
if ((currentRenderSceneOracle is null)
!= (live.RenderSceneShadow is null))
{
throw new InvalidOperationException(
"Lifecycle automation must compose the current-path referee and shadow render scene together.");
}
RenderSceneShadowComparisonController? renderSceneShadowComparison =
currentRenderSceneOracle is not null
&& live.RenderSceneShadow is not null
? new RenderSceneShadowComparisonController(
live.RenderSceneShadow,
currentRenderSceneOracle,
message => d.Log("[UI-PROBE] " + message))
: null;
live.DrawDispatcher.SetCurrentRenderSceneObserver(
currentRenderSceneOracle);
live.SelectionScene.SetCurrentRenderSceneObserver(
@ -421,7 +435,8 @@ internal sealed class FrameRootCompositionPhase
d.FrameProfiler,
content.Dats,
foundation.Residency,
currentRenderSceneOracle);
currentRenderSceneOracle,
renderSceneShadowComparison);
lifecycleAutomation =
new WorldLifecycleAutomationController(
() => session.WorldReveal.Snapshot,
@ -463,6 +478,14 @@ internal sealed class FrameRootCompositionPhase
renderFrameResources,
settings.DevTools?.Presenter,
renderWeatherFrame);
IRenderFramePostDiagnosticsPhase postDiagnostics =
renderSceneShadowComparison is not null
&& lifecycleAutomation is not null
? new SerialRenderFramePostDiagnosticsPhase(
renderSceneShadowComparison,
lifecycleAutomation)
: (IRenderFramePostDiagnosticsPhase?)lifecycleAutomation
?? NullRenderFramePostDiagnosticsPhase.Instance;
var renderFrame = new RenderFrameOrchestrator(
host.GpuFrameFlights,
new FrameProfilerGpuMeasurement(d.FrameProfiler, d.Gl),
@ -470,8 +493,7 @@ internal sealed class FrameRootCompositionPhase
worldSceneRenderer,
privatePresentation,
live.FrameDiagnostics,
(IRenderFramePostDiagnosticsPhase?)lifecycleAutomation
?? NullRenderFramePostDiagnosticsPhase.Instance,
postDiagnostics,
(IRenderFrameFailureRecovery?)settings.DevTools?.Presenter
?? NullRenderFrameFailureRecovery.Instance);
Fault(FrameRootCompositionPoint.RenderRootCreated);

View file

@ -6,6 +6,7 @@ using AcDream.App.Interaction;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Residency;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Sky;
using AcDream.App.Rendering.Vfx;
@ -80,6 +81,7 @@ internal sealed record LivePresentationResult(
RetailStaticAnimatingObjectScheduler StaticAnimationScheduler,
WorldGenerationAvailabilityState WorldAvailability,
GpuWorldState WorldState,
RenderSceneShadowRuntime? RenderSceneShadow,
LiveEntityRuntime LiveEntities,
ProjectileController ProjectileController,
LiveEntityProjectionWithdrawalController ProjectionWithdrawal,
@ -196,6 +198,20 @@ internal sealed class LivePresentationCompositionPhase
try
{
CompositionAcquisitionScope.CompositionAcquisitionLease<
RenderSceneShadowRuntime>? renderSceneShadowLease = null;
if (interaction.RetainedUi?.Screenshots is not null
&& d.Options.AutomationArtifactDirectory is not null)
{
renderSceneShadowLease = scope.Acquire(
"shadow render scene",
() => new RenderSceneShadowRuntime(
RenderSceneGeneration.FromRaw(1)),
static value => value.Dispose());
}
RenderSceneShadowRuntime? renderSceneShadow =
renderSceneShadowLease?.Resource;
var componentLifecycle =
new DeferredLiveEntityRuntimeComponentLifecycle();
var wbSpawnAdapter = new LandblockSpawnAdapter(foundation.MeshAdapter);
@ -333,20 +349,33 @@ internal sealed class LivePresentationCompositionPhase
"developer world-entity source",
devWorldEntities.BindOwned(worldState));
}
liveEntities = new LiveEntityRuntime(
worldState,
new CompositeLiveEntityResourceLifecycle(
var resourceOwners =
new List<CompositeLiveEntityResourceLifecycle.Owner>
{
new(
entity => _ = entitySpawnAdapter.OnCreate(entity),
entity => _ = entitySpawnAdapter.OnRemove(entity)),
new(
entityScriptActivator.OnCreate,
entityScriptActivator.OnRemove)),
entityScriptActivator.OnRemove),
};
if (renderSceneShadow is not null)
{
resourceOwners.Add(new(
renderSceneShadow.OnLiveResourceRegistered,
renderSceneShadow.OnLiveResourceUnregistered));
}
liveEntities = new LiveEntityRuntime(
worldState,
new CompositeLiveEntityResourceLifecycle(
[.. resourceOwners]),
componentLifecycle);
var liveRuntimeLease = scope.Own(
"canonical live-entity runtime",
liveEntities,
static runtime => runtime.Clear());
LiveRenderProjectionJournal? liveRenderProjections =
renderSceneShadow?.BindLiveRuntime(liveEntities);
Fault(LivePresentationCompositionPoint.CanonicalRuntimeCreated);
bindings.Adopt(
@ -376,6 +405,13 @@ internal sealed class LivePresentationCompositionPhase
liveEntities,
wbVisibility,
"WB projection visibility");
if (liveRenderProjections is not null)
{
bindings.BindProjectionVisibility(
liveEntities,
liveRenderProjections.OnProjectionVisibilityChanged,
"shadow render projection visibility");
}
Action<LiveEntityRecord, bool> particleVisibility = (record, visible) =>
{
if (record.WorldEntity is { } entity)
@ -511,6 +547,15 @@ internal sealed class LivePresentationCompositionPhase
bindings.BindProjectionPoseReady(
equippedLease.Resource,
lightsLease.Resource.OnAttachedPoseReady);
if (liveRenderProjections is not null)
{
bindings.BindProjectionPoseReady(
equippedLease.Resource,
liveRenderProjections.OnProjectionPoseReady);
bindings.BindProjectionRemoved(
equippedLease.Resource,
liveRenderProjections.OnProjectionRemoved);
}
bindings.Adopt(
"entity-effect animation hooks",
content.HookRegistrations.RegisterOwned(entityEffects));
@ -529,6 +574,8 @@ internal sealed class LivePresentationCompositionPhase
staticAnimationScheduler,
worldAvailability,
worldState,
renderSceneShadow,
renderSceneShadowLease,
liveEntities,
projectileController,
projectionWithdrawal,
@ -574,6 +621,9 @@ internal sealed class LivePresentationCompositionPhase
RetailStaticAnimatingObjectScheduler staticAnimationScheduler,
WorldGenerationAvailabilityState worldAvailability,
GpuWorldState worldState,
RenderSceneShadowRuntime? renderSceneShadow,
CompositionAcquisitionScope.CompositionAcquisitionLease<
RenderSceneShadowRuntime>? renderSceneShadowLease,
LiveEntityRuntime liveEntities,
ProjectileController projectileController,
LiveEntityProjectionWithdrawalController projectionWithdrawal,
@ -818,7 +868,8 @@ internal sealed class LivePresentationCompositionPhase
worldState,
landblockRetirementOwner,
landblockLoaded.OnLandblockLoaded,
landblockRenderPublisher.PrepareAfterRenderPins);
landblockRenderPublisher.PrepareAfterRenderPins,
renderSceneShadow?.StaticProjections);
Fault(LivePresentationCompositionPoint.LandblockPipelineCreated);
var clipFrameLease = scope.Acquire(
@ -965,6 +1016,7 @@ internal sealed class LivePresentationCompositionPhase
staticAnimationScheduler,
worldAvailability,
worldState,
renderSceneShadow,
liveEntities,
projectileController,
projectionWithdrawal,
@ -1010,6 +1062,7 @@ internal sealed class LivePresentationCompositionPhase
_publication.PublishLivePresentation(result);
liveRuntimeLease.Transfer();
renderSceneShadowLease?.Transfer();
lightsLease.Transfer();
equippedLease.Transfer();
presentationLease.Transfer();

View file

@ -5,6 +5,7 @@ using AcDream.App.Diagnostics;
using AcDream.App.Net;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Settings;
@ -454,7 +455,8 @@ internal sealed class SessionPlayerCompositionPhase
new LiveEntityReadyPublisher(
live.LiveEntities,
live.EntityEffects,
live.Presentation),
live.Presentation,
live.RenderSceneShadow?.LiveProjections),
originCoordinator,
networkUpdateBridge,
localPhysicsTimestamps,
@ -603,7 +605,9 @@ internal sealed class SessionPlayerCompositionPhase
live.EntityEffects,
live.EquippedChildren,
content.ParticleSink,
live.Lights);
live.Lights,
live.RenderSceneShadow?.LiveProjections,
live.RenderSceneShadow);
var localPlayerAnimation = new LocalPlayerAnimationController(
live.LiveEntities,
d.PlayerIdentity,
@ -650,7 +654,9 @@ internal sealed class SessionPlayerCompositionPhase
live.AnimationPresenter,
d.AnimatedEntities,
live.EquippedChildren,
liveEffectFrame);
liveEffectFrame,
live.RenderSceneShadow?.LiveProjections,
live.RenderSceneShadow?.StaticProjections);
Fault(SessionPlayerCompositionPoint.UpdateLeavesCreated);
var playerMode = new PlayerModeController(
@ -812,7 +818,8 @@ internal sealed class SessionPlayerCompositionPhase
live.EntityEffects,
content.AnimationHookFrames,
live.Presentation,
d.RemoteMovementObservations),
d.RemoteMovementObservations,
live.RenderSceneShadow),
d.Log);
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(liveSession);
Fault(SessionPlayerCompositionPoint.SessionHostCreated);