refactor(runtime): own world reveal generation

This commit is contained in:
Erik 2026-07-26 17:09:54 +02:00
parent 4ab98b080e
commit a6860d5563
26 changed files with 1294 additions and 502 deletions

View file

@ -5,6 +5,7 @@ using AcDream.App.Rendering.Residency;
using AcDream.App.Rendering.Scene;
using AcDream.App.Streaming;
using AcDream.App.UI.Testing;
using AcDream.Runtime;
using SixLabors.ImageSharp;
namespace AcDream.App.Tests.Diagnostics;
@ -95,10 +96,11 @@ public sealed class WorldLifecycleAutomationControllerTests
public void Checkpoint_WritesCanonicalRevealAndResourceSnapshot()
{
string directory = NewDirectory();
var reveal = new WorldRevealLifecycleSnapshot(
var reveal = new RuntimePortalSnapshot(
Generation: 7,
Kind: WorldRevealKind.Portal,
Readiness: new WorldRevealReadinessSnapshot(
Kind: RuntimePortalKind.Portal,
Readiness: new RuntimeDestinationReadiness(
Generation: 7,
0x8A020164u,
IsIndoor: true,
IsUnhydratable: false,
@ -110,7 +112,10 @@ public sealed class WorldLifecycleAutomationControllerTests
Completed: true,
Cancelled: false,
WorldViewportObserved: true,
InvariantFailureCount: 0);
WorldSimulationAvailable: true,
InvariantFailureCount: 0,
WaitCueShown: false,
PortalMaterializationCount: 3);
var resources = EmptyResources() with
{
LoadedLandblocks = 1,

View file

@ -6,6 +6,8 @@ using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Rendering;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.Rendering;
@ -25,8 +27,9 @@ public sealed class WorldSceneRendererTests
[Fact]
public void QuiescedGeneration_PublishesEmptySelectionFrameAndSkipsEveryWorldOwner()
{
var availability = new WorldGenerationAvailabilityState();
availability.Begin(4);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
var rig = new Rig(
portalVisible: false,
waitingForLogin: false,

View file

@ -65,7 +65,7 @@ public sealed class CurrentGameRuntimeAdapterTests
};
harness.Objects.AddOrUpdate(item);
harness.Chat.OnSystemMessage("runtime parity", 0x1Au);
harness.WorldReveal.Begin(WorldRevealKind.Portal, 0x12340001u);
harness.WorldReveal.Begin(RuntimePortalKind.Portal, 0x12340001u);
WorldRevealReadinessSnapshot readiness =
harness.WorldReveal.PrepareAndEvaluate(0x12340001u);
Assert.True(readiness.IsReady);
@ -792,7 +792,9 @@ public sealed class CurrentGameRuntimeAdapterTests
mouseLook: null,
new NoopCombatInput());
Clock = new UpdateFrameClock();
WorldTransit = new RuntimeWorldTransitState();
WorldReveal = new WorldRevealCoordinator(
WorldTransit,
static (_, _) => true,
static _ => true,
static (_, _) => true,
@ -836,7 +838,7 @@ public sealed class CurrentGameRuntimeAdapterTests
Actions,
MovementState,
Environment,
WorldReveal,
WorldTransit,
Clock,
selectionController);
}
@ -861,6 +863,7 @@ public sealed class CurrentGameRuntimeAdapterTests
public GameplayInputFrameController GameplayInput { get; }
public RecordingCombatModeOperations CombatMode { get; }
public UpdateFrameClock Clock { get; }
public RuntimeWorldTransitState WorldTransit { get; }
public WorldRevealCoordinator WorldReveal { get; }
public RecordingCommandRouting Commands { get; }
public TestTransport Transport { get; }

View file

@ -0,0 +1,91 @@
using System.Text.RegularExpressions;
using AcDream.App.Runtime;
using AcDream.App.Streaming;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.Runtime;
public sealed class RuntimeWorldTransitOwnershipTests
{
[Fact]
public void ProductionConstructsOneRuntimeTransitOwnerAndNoAppLifecycleOwner()
{
string root = FindRepositoryRoot();
string appRoot = Path.Combine(root, "src", "AcDream.App");
string app = string.Join(
"\n",
Directory.EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories)
.Select(File.ReadAllText));
Assert.False(File.Exists(Path.Combine(
appRoot,
"Streaming",
"WorldRevealLifecycleTelemetry.cs")));
Assert.DoesNotContain(
"class WorldRevealLifecycleTelemetry",
app,
StringComparison.Ordinal);
Assert.DoesNotContain(
"record struct WorldRevealLifecycleSnapshot",
app,
StringComparison.Ordinal);
Assert.Single(Regex.Matches(
app,
@"new\s+RuntimeWorldTransitState\s*\(")
.Cast<Match>());
}
[Fact]
public void GraphicalAdaptersBorrowRuntimeStateWithoutReconstructingPortalView()
{
var availabilityFields = typeof(WorldGenerationAvailabilityState)
.GetFields(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
Assert.Single(availabilityFields);
Assert.Equal(
typeof(RuntimeWorldTransitState),
availabilityFields[0].FieldType);
var viewFields = typeof(CurrentGameRuntimeViewAdapter)
.GetFields(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
Assert.Contains(
viewFields,
field => field.FieldType == typeof(IRuntimePortalView));
Assert.DoesNotContain(
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
System.Reflection.BindingFlags.NonPublic),
type => type.Name.Contains(
"PortalView",
StringComparison.Ordinal));
var coordinatorFields = typeof(WorldRevealCoordinator)
.GetFields(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
Assert.Contains(
coordinatorFields,
field => field.FieldType == typeof(RuntimeWorldTransitState));
Assert.DoesNotContain(
coordinatorFields,
field => field.Name is "_activeGeneration"
or "_worldSimulationReleased"
or "_lifecycle");
}
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "AcDream.slnx")))
return current.FullName;
current = current.Parent;
}
throw new DirectoryNotFoundException("AcDream.slnx was not found.");
}
}

View file

@ -5,7 +5,9 @@ using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.World;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Tests.Streaming;
@ -617,8 +619,10 @@ public sealed class GpuWorldStateVisibilityTests
const uint landblock = 0x0101FFFFu;
const uint cell = 0x01010001u;
const uint guid = 0x70000021u;
var availability = new WorldGenerationAvailabilityState();
availability.Begin(7);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, cell);
var state = new GpuWorldState(availability: availability);
state.AddLandblock(new LoadedLandblock(
landblock,
@ -644,7 +648,18 @@ public sealed class GpuWorldStateVisibilityTests
Assert.True(record.IsSpatiallyVisible);
Assert.Equal([true], edges);
Assert.True(availability.End(7));
Assert.True(transit.AcknowledgeDestinationReadiness(
new RuntimeDestinationReadiness(
generation,
cell,
IsIndoor: false,
IsUnhydratable: false,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
Assert.True(transit.AcknowledgeMaterialized(generation, cell));
Assert.True(transit.Complete(generation));
Assert.True(state.IsLiveEntityVisible(projectionKey));
Assert.True(record.IsSpatiallyVisible);
}
@ -688,8 +703,9 @@ public sealed class GpuWorldStateVisibilityTests
[Fact]
public void ReadinessNeighborhood_CopiesStaticAndLiveWhileWorldIsQuiesced()
{
var availability = new WorldGenerationAvailabilityState();
availability.Begin(7);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x10100001u);
var centerStatic = Entity(1, 0u);
var centerLive = Entity(2, 0x70000001u);
var neighborStatic = Entity(3, 0u);

View file

@ -8,6 +8,8 @@ using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.Streaming;
@ -57,7 +59,7 @@ public sealed class LocalPlayerTeleportControllerTests
(1L, 0x20210001u, WorldRevealReadinessBarrier.OutdoorNeighborhoodRadius),
Assert.Single(harness.Streaming.Reservations));
Assert.True(harness.Reveal.Snapshot.IsActive);
Assert.Equal(WorldRevealKind.Portal, harness.Reveal.Snapshot.Kind);
Assert.Equal(RuntimePortalKind.Portal, harness.Reveal.Snapshot.Kind);
harness.Controller.OfferDestination(
destination,
@ -310,7 +312,7 @@ public sealed class LocalPlayerTeleportControllerTests
harness.Controller.ResetSession();
Assert.False(harness.Controller.IsActive);
Assert.True(harness.Reveal.Snapshot.Cancelled);
Assert.Equal(RuntimePortalSnapshot.Idle, harness.Reveal.Snapshot);
Assert.True(harness.Streaming.LastResetWasSessionEnding);
var failed = new Harness();
@ -665,6 +667,7 @@ public sealed class LocalPlayerTeleportControllerTests
Session = new FakeSession(order);
Presentation = new FakePresentation(order);
Reveal = new WorldRevealCoordinator(
new RuntimeWorldTransitState(order.Add),
isRenderNeighborhoodReady: (_, _) => worldReady,
isSpawnCellReady: _ => worldReady,
isTerrainNeighborhoodReady: (_, _) => worldReady,
@ -672,7 +675,6 @@ public sealed class LocalPlayerTeleportControllerTests
prepareCompositeTextures: (_, _) => { },
invalidateCompositeTextures: () => { },
isSpawnClaimUnhydratable: _ => false,
log: order.Add,
streaming: Streaming);
Controller = new LocalPlayerTeleportController(
Authority,

View file

@ -3,7 +3,9 @@ using AcDream.App.Audio;
using AcDream.App.Streaming;
using AcDream.Core.Selection;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.World;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Tests.Streaming;
@ -11,12 +13,14 @@ namespace AcDream.App.Tests.Streaming;
public sealed class WorldGenerationQuiescenceTests
{
private const uint LandblockId = 0x1010FFFFu;
private const uint DestinationCell = 0x10100001u;
private const uint ServerGuid = 0x70000001u;
[Fact]
public void ActiveGeneration_HidesWorldQueriesButRetainsCanonicalOwners()
public void RuntimeGeneration_HidesWorldQueriesButRetainsCanonicalOwners()
{
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
WorldEntity entity = Entity();
world.AddLandblock(new LoadedLandblock(
@ -32,10 +36,12 @@ public sealed class WorldGenerationQuiescenceTests
Assert.Single(world.LandblockEntries);
Assert.Single(world.LandblockBounds);
availability.Begin(17);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
Assert.False(availability.IsWorldAvailable);
Assert.Equal(generation, availability.QuiescedGeneration);
Assert.False(world.IsLiveEntityVisible(key));
Assert.Empty(world.LandblockEntries);
Assert.Empty(world.LandblockBounds);
@ -43,17 +49,25 @@ public sealed class WorldGenerationQuiescenceTests
Assert.Same(entity, Assert.Single(world.Entities));
Assert.True(world.IsLoaded(LandblockId));
Assert.False(availability.End(16));
Assert.False(transit.Complete(generation + 1));
Assert.False(availability.IsWorldAvailable);
Assert.True(availability.End(17));
Assert.True(transit.AcknowledgeDestinationReadiness(
Ready(generation, DestinationCell)));
Assert.True(transit.AcknowledgeMaterialized(
generation,
DestinationCell));
Assert.True(transit.Complete(generation));
Assert.True(world.IsLiveEntityVisible(key));
Assert.Same(entity, Assert.Single(world.LandblockEntries).Entities.Single());
Assert.Same(
entity,
Assert.Single(world.LandblockEntries).Entities.Single());
}
[Fact]
public void SupersededReveal_StaysContinuouslySilentAndOnlyLatestGenerationReopens()
public void SupersededReveal_StaysSilentAndOnlyLatestRuntimeEdgeResumesAudio()
{
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
world.AddLandblock(new LoadedLandblock(
LandblockId,
@ -65,23 +79,36 @@ public sealed class WorldGenerationQuiescenceTests
selection.Select(ServerGuid, SelectionChangeSource.World);
var audio = new RecordingAudioQuiescence();
var quiescence = new WorldGenerationQuiescence(
availability,
selection,
world,
guid => guid == ServerGuid ? Key(entity) : null,
audio);
quiescence.Begin(1);
quiescence.Begin(2);
quiescence.End(1);
WorldGenerationQuiescenceEdge firstEdge =
quiescence.CaptureBegin();
long first =
transit.BeginReveal(RuntimePortalKind.Login, DestinationCell);
quiescence.CommitBegin(firstEdge);
WorldGenerationQuiescenceEdge secondEdge =
quiescence.CaptureBegin();
long second =
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
quiescence.CommitBegin(secondEdge);
Assert.False(transit.Complete(first));
Assert.False(availability.IsWorldAvailable);
Assert.Equal(2, availability.QuiescedGeneration);
Assert.Equal(second, availability.QuiescedGeneration);
Assert.Null(selection.SelectedObjectId);
Assert.Equal(1, audio.SuspendCalls);
Assert.Equal(0, audio.ResumeCalls);
quiescence.End(2);
Assert.True(transit.AcknowledgeDestinationReadiness(
Ready(second, DestinationCell)));
Assert.True(transit.AcknowledgeMaterialized(
second,
DestinationCell));
Assert.True(transit.Complete(second));
quiescence.ObserveReleased();
Assert.True(availability.IsWorldAvailable);
Assert.Equal(1, audio.ResumeCalls);
@ -91,22 +118,37 @@ public sealed class WorldGenerationQuiescenceTests
public void Quiesce_PreservesNonWorldInventorySelection()
{
const uint inventoryGuid = 0x80000001u;
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
var selection = new SelectionState();
selection.Select(inventoryGuid, SelectionChangeSource.Inventory);
var quiescence = new WorldGenerationQuiescence(
availability,
selection,
world,
_ => null,
audio: null);
quiescence.Begin(3);
WorldGenerationQuiescenceEdge edge = quiescence.CaptureBegin();
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
quiescence.CommitBegin(edge);
Assert.Equal(inventoryGuid, selection.SelectedObjectId);
}
private static RuntimeDestinationReadiness Ready(
long generation,
uint destinationCell) =>
new(
generation,
destinationCell,
IsIndoor: false,
IsUnhydratable: false,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true);
private static WorldEntity Entity() => new()
{
Id = 1,

View file

@ -1,6 +1,8 @@
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Selection;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.Streaming;
@ -17,21 +19,27 @@ public sealed class WorldRevealCoordinatorTests
public WorldRevealCoordinator Build(
List<string>? logs = null,
IWorldRevealStreamingScheduler? streaming = null,
IWorldRevealRenderResourceScheduler? renderResources = null) => new(
isRenderNeighborhoodReady: (_, _) => RenderReady,
isSpawnCellReady: _ => CollisionReady,
isTerrainNeighborhoodReady: (_, _) => CollisionReady,
areCompositeTexturesReady: () => CompositesReady,
prepareCompositeTextures: (_, _) => PrepareCount++,
invalidateCompositeTextures: () =>
{
InvalidateCount++;
CompositesReady = false;
},
isSpawnClaimUnhydratable: _ => false,
log: logs is null ? null : new Action<string>(logs.Add),
streaming: streaming,
renderResources: renderResources);
IWorldRevealRenderResourceScheduler? renderResources = null,
RuntimeWorldTransitState? transit = null)
{
transit ??= new RuntimeWorldTransitState(
logs is null ? null : new Action<string>(logs.Add));
return new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => RenderReady,
isSpawnCellReady: _ => CollisionReady,
isTerrainNeighborhoodReady: (_, _) => CollisionReady,
areCompositeTexturesReady: () => CompositesReady,
prepareCompositeTextures: (_, _) => PrepareCount++,
invalidateCompositeTextures: () =>
{
InvalidateCount++;
CompositesReady = false;
},
isSpawnClaimUnhydratable: _ => false,
streaming: streaming,
renderResources: renderResources);
}
}
[Fact]
@ -40,12 +48,12 @@ public sealed class WorldRevealCoordinatorTests
var state = new State { CompositesReady = true };
WorldRevealCoordinator coordinator = state.Build();
long generation = coordinator.Begin(WorldRevealKind.Login, 0x11340021u);
long generation = coordinator.Begin(RuntimePortalKind.Login, 0x11340021u);
Assert.Equal(1, generation);
Assert.Equal(1, state.InvalidateCount);
Assert.False(state.CompositesReady);
Assert.Equal(WorldRevealKind.Login, coordinator.Snapshot.Kind);
Assert.Equal(RuntimePortalKind.Login, coordinator.Snapshot.Kind);
}
[Fact]
@ -58,7 +66,7 @@ public sealed class WorldRevealCoordinatorTests
CollisionReady = true,
};
WorldRevealCoordinator coordinator = state.Build();
coordinator.Begin(WorldRevealKind.Login, outdoorCell);
coordinator.Begin(RuntimePortalKind.Login, outdoorCell);
WorldRevealReadinessSnapshot first = coordinator.PrepareAndEvaluate(outdoorCell);
state.CompositesReady = true;
@ -67,7 +75,8 @@ public sealed class WorldRevealCoordinatorTests
Assert.False(first.IsReady);
Assert.True(ready.IsReady);
Assert.Equal(2, state.PrepareCount);
Assert.Equal(ready, coordinator.Snapshot.Readiness);
Assert.Equal(ready.DestinationCell, coordinator.Snapshot.DestinationCell);
Assert.Equal(ready.IsReady, coordinator.Snapshot.IsReady);
}
[Fact]
@ -82,7 +91,7 @@ public sealed class WorldRevealCoordinatorTests
CompositesReady = true,
};
WorldRevealCoordinator coordinator = state.Build(logs);
coordinator.Begin(WorldRevealKind.Portal, outdoorCell);
coordinator.Begin(RuntimePortalKind.Portal, outdoorCell);
state.CompositesReady = true;
Assert.True(coordinator.Evaluate(outdoorCell).IsReady);
@ -109,7 +118,7 @@ public sealed class WorldRevealCoordinatorTests
CompositesReady = true,
};
WorldRevealCoordinator coordinator = state.Build();
coordinator.Begin(WorldRevealKind.Portal, 0x11340021u);
coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u);
coordinator.Cancel();
coordinator.PrepareAndEvaluate(0x11340021u);
@ -118,23 +127,25 @@ public sealed class WorldRevealCoordinatorTests
coordinator.Complete();
Assert.True(coordinator.Snapshot.Cancelled);
Assert.Equal(default, coordinator.Snapshot.Readiness);
Assert.Equal(0x11340021u, coordinator.Snapshot.DestinationCell);
Assert.False(coordinator.Snapshot.IsReady);
Assert.Equal(0, coordinator.PortalMaterializationCount);
}
[Fact]
public void RevealLifetime_QuiescesContinuouslyAndOnlyActiveGenerationReopens()
{
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
var audio = new RecordingAudioQuiescence();
var quiescence = new WorldGenerationQuiescence(
availability,
new SelectionState(),
world,
_ => null,
audio);
var coordinator = new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => true,
isSpawnCellReady: _ => true,
isTerrainNeighborhoodReady: (_, _) => true,
@ -144,12 +155,14 @@ public sealed class WorldRevealCoordinatorTests
isSpawnClaimUnhydratable: _ => false,
quiescence: quiescence);
Assert.Equal(1, coordinator.Begin(WorldRevealKind.Login, 0x11340021u));
Assert.Equal(2, coordinator.Begin(WorldRevealKind.Portal, 0x11340021u));
Assert.Equal(1, coordinator.Begin(RuntimePortalKind.Login, 0x11340021u));
Assert.Equal(2, coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u));
Assert.False(availability.IsWorldAvailable);
Assert.Equal(2, availability.QuiescedGeneration);
Assert.Equal(1, audio.SuspendCalls);
Assert.True(coordinator.Evaluate(0x11340021u).IsReady);
coordinator.ObserveMaterialized();
coordinator.Complete();
Assert.True(availability.IsWorldAvailable);
@ -159,17 +172,18 @@ public sealed class WorldRevealCoordinatorTests
[Fact]
public void Materialization_ReopensWorldBeforePortalViewportRelease()
{
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
var audio = new RecordingAudioQuiescence();
var quiescence = new WorldGenerationQuiescence(
availability,
new SelectionState(),
world,
_ => null,
audio);
var scheduler = new RecordingDestinationScheduler();
var coordinator = new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => true,
isSpawnCellReady: _ => true,
isTerrainNeighborhoodReady: (_, _) => true,
@ -180,10 +194,11 @@ public sealed class WorldRevealCoordinatorTests
quiescence: quiescence,
streaming: scheduler);
long generation = coordinator.Begin(
WorldRevealKind.Portal,
RuntimePortalKind.Portal,
0x3032001Cu);
Assert.False(availability.IsWorldAvailable);
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
coordinator.ObserveMaterialized();
@ -211,16 +226,17 @@ public sealed class WorldRevealCoordinatorTests
[Fact]
public void TunnelContinue_TicksDestinationObjectsBehindPortalViewport()
{
var availability = new WorldGenerationAvailabilityState();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
var quiescence = new WorldGenerationQuiescence(
availability,
new SelectionState(),
world,
_ => null,
audio: null);
var scheduler = new RecordingDestinationScheduler();
var coordinator = new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => true,
isSpawnCellReady: _ => true,
isTerrainNeighborhoodReady: (_, _) => true,
@ -243,11 +259,12 @@ public sealed class WorldRevealCoordinatorTests
() => calls.Add("spatial")),
availability);
coordinator.Begin(WorldRevealKind.Portal, 0x3032001Cu);
coordinator.Begin(RuntimePortalKind.Portal, 0x3032001Cu);
frame.Tick(1f / 60f);
Assert.Equal(["network", "commands"], calls);
calls.Clear();
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
coordinator.ObserveMaterialized();
frame.Tick(1f / 60f);
@ -256,6 +273,43 @@ public sealed class WorldRevealCoordinatorTests
Assert.False(coordinator.Snapshot.WorldViewportObserved);
}
[Fact]
public void SessionReset_ReleasesHostEdgesAndClearsRuntimeSnapshot()
{
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
var world = new GpuWorldState(availability: availability);
var audio = new RecordingAudioQuiescence();
var quiescence = new WorldGenerationQuiescence(
new SelectionState(),
world,
_ => null,
audio);
var scheduler = new RecordingDestinationScheduler();
var coordinator = new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => false,
isSpawnCellReady: _ => false,
isTerrainNeighborhoodReady: (_, _) => false,
areCompositeTexturesReady: () => false,
prepareCompositeTextures: (_, _) => { },
invalidateCompositeTextures: () => { },
isSpawnClaimUnhydratable: _ => false,
quiescence: quiescence,
streaming: scheduler);
long generation = coordinator.Begin(
RuntimePortalKind.Portal,
0x3032001Cu);
coordinator.ResetSession();
Assert.Equal(RuntimePortalSnapshot.Idle, coordinator.Snapshot);
Assert.True(availability.IsWorldAvailable);
Assert.Equal(1, audio.SuspendCalls);
Assert.Equal(1, audio.ResumeCalls);
Assert.Equal([generation], scheduler.Ends);
}
[Fact]
public void RevealGeneration_OwnsExactDestinationReservationUntilCompletion()
{
@ -264,10 +318,10 @@ public sealed class WorldRevealCoordinatorTests
WorldRevealCoordinator coordinator = state.Build(streaming: scheduler);
long first = coordinator.Begin(
WorldRevealKind.Portal,
RuntimePortalKind.Portal,
0x11340021u);
long second = coordinator.Begin(
WorldRevealKind.Portal,
RuntimePortalKind.Portal,
0x20210123u);
Assert.Equal(
@ -289,10 +343,10 @@ public sealed class WorldRevealCoordinatorTests
state.Build(renderResources: resources);
long first = coordinator.Begin(
WorldRevealKind.Portal,
RuntimePortalKind.Portal,
0x11340021u);
long second = coordinator.Begin(
WorldRevealKind.Portal,
RuntimePortalKind.Portal,
0x20210123u);
Assert.Equal([first, second], resources.Begins);

View file

@ -1,105 +0,0 @@
using AcDream.App.Streaming;
namespace AcDream.App.Tests.Streaming;
public sealed class WorldRevealLifecycleTelemetryTests
{
private static WorldRevealReadinessSnapshot Ready(uint cell = 0x11340021u) => new(
cell,
IsIndoor: false,
IsUnhydratable: false,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true);
[Fact]
public void Login_RecordsReadyThenFirstVisibleFrameExactlyOnce()
{
var logs = new List<string>();
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
long generation = telemetry.Begin(WorldRevealKind.Login);
telemetry.ObserveReadiness(Ready());
telemetry.ObserveWorldViewportVisible();
telemetry.ObserveWorldViewportVisible();
telemetry.Complete();
Assert.Equal(generation, telemetry.Snapshot.Generation);
Assert.True(telemetry.Snapshot.IsReady);
Assert.True(telemetry.Snapshot.WorldViewportObserved);
Assert.True(telemetry.Snapshot.Completed);
Assert.Equal(0, telemetry.Snapshot.InvariantFailureCount);
Assert.Single(logs, line => line.Contains("event=world-visible", StringComparison.Ordinal));
}
[Fact]
public void EarlyWorldViewport_IsARecordedHardInvariantFailure()
{
var logs = new List<string>();
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
telemetry.Begin(WorldRevealKind.Login);
telemetry.ObserveReadiness(Ready() with { AreCompositeTexturesReady = false });
telemetry.ObserveWorldViewportVisible();
Assert.Equal(1, telemetry.Snapshot.InvariantFailureCount);
Assert.Contains(logs, line => line.Contains("reason=viewport-before-ready", StringComparison.Ordinal));
}
[Fact]
public void NewGeneration_SupersedesOldStateAndRejectsDestinationMutation()
{
var logs = new List<string>();
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
long first = telemetry.Begin(WorldRevealKind.Login);
telemetry.ObserveReadiness(Ready(0x11340021u));
long second = telemetry.Begin(WorldRevealKind.Portal);
telemetry.ObserveReadiness(Ready(0x3032001Cu));
telemetry.ObserveReadiness(Ready(0xC95B0001u));
Assert.True(second > first);
Assert.Equal(WorldRevealKind.Portal, telemetry.Snapshot.Kind);
Assert.Equal(0xC95B0001u, telemetry.Snapshot.Readiness.DestinationCell);
Assert.Equal(1, telemetry.Snapshot.InvariantFailureCount);
Assert.Contains(logs, line => line.Contains("event=superseded", StringComparison.Ordinal));
Assert.Contains(logs, line => line.Contains("reason=destination-changed", StringComparison.Ordinal));
}
[Fact]
public void PortalMaterialization_IsCountedOncePerGeneration()
{
var telemetry = new WorldRevealLifecycleTelemetry();
telemetry.Begin(WorldRevealKind.Portal);
telemetry.ObserveReadiness(Ready());
telemetry.ObserveMaterialized();
telemetry.ObserveMaterialized();
telemetry.Begin(WorldRevealKind.Portal);
telemetry.ObserveReadiness(Ready(0x3032001Cu));
telemetry.ObserveMaterialized();
Assert.Equal(2, telemetry.PortalMaterializationCount);
}
[Fact]
public void CancelledGeneration_IgnoresLateAsyncObservations()
{
var telemetry = new WorldRevealLifecycleTelemetry();
telemetry.Begin(WorldRevealKind.Portal);
telemetry.Cancel();
telemetry.ObserveReadiness(Ready());
telemetry.ObserveMaterialized();
telemetry.ObserveWorldViewportVisible();
telemetry.Complete();
Assert.True(telemetry.Snapshot.Cancelled);
Assert.False(telemetry.Snapshot.IsReady);
Assert.False(telemetry.Snapshot.Materialized);
Assert.False(telemetry.Snapshot.WorldViewportObserved);
Assert.False(telemetry.Snapshot.Completed);
Assert.Equal(0, telemetry.PortalMaterializationCount);
}
}

View file

@ -2,6 +2,7 @@ using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Runtime.World;
using AcDream.Core.World;
namespace AcDream.App.Tests.World;
@ -80,6 +81,7 @@ public sealed class LiveEntityWorldOriginCoordinatorTests
farRadius: 2);
private static WorldRevealCoordinator Reveal() => new(
new RuntimeWorldTransitState(),
(_, _) => true,
_ => true,
(_, _) => true,

View file

@ -6,6 +6,8 @@ using AcDream.App.World;
using AcDream.Content.Vfx;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.World;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
@ -42,8 +44,9 @@ public sealed class RecallTeleportAnimationTests
public void LiveFrame_WhileCellsAreBlocked_DrainsNetworkAndCommandsWithoutWorldTicks()
{
var calls = new List<string>();
var availability = new WorldGenerationAvailabilityState();
availability.Begin(1);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
new GpuWorldState(availability: availability),
@ -61,8 +64,9 @@ public sealed class RecallTeleportAnimationTests
public void LiveFrame_WhileCellsAreBlocked_SynchronizesOnlyRenderProjection()
{
var calls = new List<string>();
var availability = new WorldGenerationAvailabilityState();
availability.Begin(1);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
new GpuWorldState(availability: availability),

View file

@ -3,6 +3,8 @@ using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.World;
@ -137,8 +139,10 @@ public sealed class UpdateFrameOrchestratorTests
{
var calls = new List<string>();
var observed = new FrameObservations();
var availability = new WorldGenerationAvailabilityState();
availability.Begin(7);
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
UpdateFrameOrchestrator frame = Create(
calls,
observed: observed,
@ -146,7 +150,20 @@ public sealed class UpdateFrameOrchestratorTests
frame.Tick(new UpdateFrameInput(0.25));
frame.Tick(new UpdateFrameInput(0.25));
availability.End(7);
Assert.True(transit.AcknowledgeDestinationReadiness(
new RuntimeDestinationReadiness(
generation,
0x11340021u,
IsIndoor: false,
IsUnhydratable: false,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
Assert.True(transit.AcknowledgeMaterialized(
generation,
0x11340021u));
transit.Complete(generation);
frame.Tick(new UpdateFrameInput(0.25));
Assert.Equal([0.0, 0.0, 0.25], observed.PublishedTimes);