refactor(streaming): complete landblock presentation cutover

Remove the legacy GameWindow apply path and make the concrete render, physics, and static publishers the only production owner graph. Serialize full-window retirement with shared-origin teleport and session boundaries so old coordinate-frame resources cannot survive into a new world or login.
This commit is contained in:
Erik 2026-07-21 22:47:30 +02:00
parent 801d8a189c
commit c79d0a49da
12 changed files with 1373 additions and 402 deletions

View file

@ -101,6 +101,66 @@ public sealed class LiveSessionResetPlanTests
Assert.Equal(2, secondCalls);
}
[Fact]
public void Execute_BlocksNextSessionUntilOriginRetirementConverges()
{
const uint sourceId = 0x5353FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x53, 0x53));
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected ending-session failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
int identityResets = 0;
var plan = new LiveSessionResetPlan(
[
new("teleport transit", () =>
{
if (!recenter.Reset(sessionEnding: true))
{
throw new InvalidOperationException(
"streaming-origin retirement remains pending");
}
}),
new("session identity", () =>
{
identityResets++;
origin.Reset();
}),
]);
AggregateException blocked = Assert.Throws<AggregateException>(plan.Execute);
LiveSessionResetStageException failure = Assert.IsType<LiveSessionResetStageException>(
Assert.Single(blocked.InnerExceptions));
Assert.Equal("teleport transit", failure.StageName);
Assert.Equal(1, identityResets);
failRetirement = false;
plan.Execute();
Assert.Equal(2, identityResets);
Assert.False(recenter.IsPending);
Assert.False(origin.IsKnown);
Assert.True(origin.TryInitialize(0x61, 0x62));
Assert.Equal((0x61, 0x62), (origin.CenterX, origin.CenterY));
}
[Fact]
public void Execute_ReentrantAttemptIsReportedButLaterStagesStillRun()
{

View file

@ -232,7 +232,7 @@ public sealed class LandblockBuildOriginTests
}
[Fact]
public void BuildFactoryAndGameWindowPublication_UseCapturedOriginInsteadOfLiveCenter()
public void BuildFactoryAndRenderPublisher_UseCapturedOriginWithoutGameWindowFacade()
{
string root = FindRepoRoot();
string gameWindowSource = File.ReadAllText(Path.Combine(
@ -253,11 +253,12 @@ public sealed class LandblockBuildOriginTests
"AcDream.App",
"Streaming",
"LandblockRenderPublisher.cs"));
string publicationSection = Slice(
gameWindowSource,
"private void ApplyLoadedTerrainLocked(",
"private void OnUpdate(double dt)");
string recenterSource = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Streaming",
"StreamingOriginRecenterCoordinator.cs"));
Assert.Contains("request.Origin", buildSource, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterX", buildSource, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterY", buildSource, StringComparison.Ordinal);
@ -277,12 +278,68 @@ public sealed class LandblockBuildOriginTests
"BuildPhysicsDatBundle",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"ApplyLoadedTerrain",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"PublishLandblockStaticLightingBeforeCollision",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_landblockPhysicsPublisher!.RemoveLandblock",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_landblockRenderPublisher",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_landblockPhysicsPublisher",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_landblockStaticPresentationPublisher",
gameWindowSource,
StringComparison.Ordinal);
Assert.Contains(
"_landblockPresentationPipeline",
gameWindowSource,
StringComparison.Ordinal);
Assert.Contains(
"new AcDream.App.Streaming.LandblockRenderPublisher(",
gameWindowSource,
StringComparison.Ordinal);
Assert.Contains(
"new AcDream.App.Streaming.LandblockPhysicsPublisher(",
gameWindowSource,
StringComparison.Ordinal);
Assert.Contains(
"new AcDream.App.Streaming.LandblockStaticPresentationPublisher(",
gameWindowSource,
StringComparison.Ordinal);
Assert.DoesNotContain("applyTerrain:", gameWindowSource, StringComparison.Ordinal);
Assert.DoesNotContain("demoteNearLayer:", gameWindowSource, StringComparison.Ordinal);
Assert.DoesNotContain("retirementCoordinator:", gameWindowSource, StringComparison.Ordinal);
Assert.DoesNotContain(
"_liveWorldOrigin.Recenter(lbX, lbY)",
gameWindowSource,
StringComparison.Ordinal);
int retirementBarrier = recenterSource.IndexOf(
"IsOriginRecenterRetirementComplete()",
StringComparison.Ordinal);
int originCommit = recenterSource.IndexOf(
"_origin.Recenter(",
StringComparison.Ordinal);
int destinationCommit = recenterSource.IndexOf(
"_streaming.TryCommitOriginRecenter(",
StringComparison.Ordinal);
Assert.True(retirementBarrier >= 0);
Assert.True(originCommit > retirementBarrier);
Assert.True(destinationCommit > originCommit);
Assert.Contains("ComputeOrigin(landblockId, build.Origin)", renderPublisherSource, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterX", renderPublisherSource, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterY", renderPublisherSource, StringComparison.Ordinal);
Assert.Contains("renderPublication.Origin", publicationSection, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterX", publicationSection, StringComparison.Ordinal);
Assert.DoesNotContain("_liveCenterY", publicationSection, StringComparison.Ordinal);
}
private static LandblockBuild EmptyBuild(uint landblockId, LandblockBuildOrigin origin) =>
@ -316,15 +373,6 @@ public sealed class LandblockBuildOriginTests
return results;
}
private static string Slice(string source, string startMarker, string endMarker)
{
int start = source.IndexOf(startMarker, StringComparison.Ordinal);
Assert.True(start >= 0, $"Missing source marker: {startMarker}");
int end = source.IndexOf(endMarker, start, StringComparison.Ordinal);
Assert.True(end > start, $"Missing source marker: {endMarker}");
return source[start..end];
}
private static string FindRepoRoot()
{
string? dir = AppContext.BaseDirectory;

View file

@ -3,6 +3,7 @@ using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Terrain;
using AcDream.Core.World;
using DatReaderWriter.DBObjs;
@ -47,7 +48,7 @@ public sealed class LandblockPresentationPipelineTests
}
[Fact]
public void ControllerConcreteConstructorHasNoLegacyPresentationParameters()
public void PublicPresentationConstructorsExposeOnlyConcreteOwnerGraph()
{
string[] legacyNames =
[
@ -59,14 +60,595 @@ public sealed class LandblockPresentationPipelineTests
"retirementCoordinator",
];
System.Reflection.ConstructorInfo concrete = Assert.Single(
typeof(StreamingController).GetConstructors(),
constructor => constructor.GetParameters().Any(
parameter => parameter.Name == "presentationPipeline"));
string[] names = concrete.GetParameters()
System.Reflection.ConstructorInfo controller = Assert.Single(
typeof(StreamingController).GetConstructors());
Assert.Contains(
controller.GetParameters(),
parameter => parameter.Name == "presentationPipeline");
string[] names = controller.GetParameters()
.Select(parameter => parameter.Name!)
.ToArray();
Assert.DoesNotContain(names, legacyNames.Contains);
System.Reflection.ConstructorInfo pipeline = Assert.Single(
typeof(LandblockPresentationPipeline).GetConstructors());
Type[] pipelineParameterTypes = pipeline.GetParameters()
.Select(parameter => parameter.ParameterType)
.ToArray();
Assert.Contains(typeof(LandblockRenderPublisher), pipelineParameterTypes);
Assert.Contains(typeof(LandblockPhysicsPublisher), pipelineParameterTypes);
Assert.Contains(typeof(LandblockStaticPresentationPublisher), pipelineParameterTypes);
Assert.DoesNotContain(
pipeline.GetParameters(),
parameter => parameter.Name == "publishBeforeSpatialCommit");
string[] coordinatorOnlyMethods =
[
"BeginOriginRecenter",
"IsOriginRecenterRetirementComplete",
"TryCommitOriginRecenter",
"TryCancelOriginRecenter",
];
foreach (string methodName in coordinatorOnlyMethods)
{
Assert.Null(typeof(StreamingController).GetMethod(methodName));
Assert.NotNull(typeof(StreamingController).GetMethod(
methodName,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic));
}
}
[Fact]
public void OriginRecenter_BlocksOriginAndBootstrapUntilEveryOldOwnerConverges()
{
const uint centerId = 0x1919FFFFu;
const uint neighborId = 0x191AFFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
centerId,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
neighborId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x19, 0x19));
var enqueued = new List<uint>();
bool failNeighbor = true;
int neighborAttempts = 0;
var controller = new StreamingController(
enqueueLoad: (id, _) => enqueued.Add(id),
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: id =>
{
if (id != neighborId)
return;
neighborAttempts++;
if (failNeighbor)
throw new InvalidOperationException("injected non-center failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x22, 0x23, isSealedDungeon: false));
Assert.Equal((0x19, 0x19), (origin.CenterX, origin.CenterY));
Assert.False(state.IsLoaded(centerId));
Assert.False(state.IsLoaded(neighborId));
Assert.Equal(1, controller.PendingRetirementCount);
Assert.Equal(2, controller.LastFullWindowRetirementLandblockCount);
controller.Tick(0x19, 0x19);
Assert.Empty(enqueued);
Assert.True(neighborAttempts >= 2);
failNeighbor = false;
Assert.True(recenter.Advance());
Assert.Equal((0x22, 0x23), (origin.CenterX, origin.CenterY));
Assert.Equal(0, controller.PendingRetirementCount);
controller.Tick(0x22, 0x23);
Assert.Equal([0x2223FFFFu], enqueued);
}
[Fact]
public void OriginRecenter_PreparationFailureBeforeDetachRetainsOldOriginAndResidents()
{
const uint landblockId = 0x3030FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblockId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x30, 0x30));
bool failClear = true;
int clearAttempts = 0;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
clearPendingLoads: () =>
{
clearAttempts++;
if (failClear)
throw new InvalidOperationException("injected clear failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x40, 0x41, isSealedDungeon: false));
Assert.Equal((0x30, 0x30), (origin.CenterX, origin.CenterY));
Assert.True(state.IsLoaded(landblockId));
Assert.Equal(0, controller.PendingRetirementCount);
failClear = false;
Assert.True(recenter.Advance());
Assert.Equal(2, clearAttempts);
Assert.False(state.IsLoaded(landblockId));
Assert.Equal((0x40, 0x41), (origin.CenterX, origin.CenterY));
}
[Fact]
public void OriginRecenter_ObserverFailureHalfwayRetainsBarrierUntilSnapshotFinishes()
{
const uint firstId = 0x4242FFFFu;
const uint secondId = 0x4243FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
firstId,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
secondId,
new LandBlock(),
[Entity(2, serverGuid: 0x70000042u)]));
int observerCalls = 0;
state.LiveProjectionVisibilityChanged += (_, _) =>
{
observerCalls++;
throw new InvalidOperationException("injected observer failure");
};
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x42, 0x42));
var removed = new List<uint>();
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: id => removed.Add(id));
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x43, 0x44, isSealedDungeon: false));
Assert.Equal((0x42, 0x42), (origin.CenterX, origin.CenterY));
Assert.False(state.IsLoaded(firstId));
Assert.False(state.IsLoaded(secondId));
Assert.Equal(1, observerCalls);
Assert.True(recenter.Advance());
Assert.Equal((0x43, 0x44), (origin.CenterX, origin.CenterY));
Assert.Equal([firstId, secondId], removed);
}
[Fact]
public void OriginRecenter_RetryPreservesLiveIdentityAndDoesNotRescueReusedGuid()
{
const uint oldId = 0x4545FFFFu;
const uint destinationId = 0x4646FFFFu;
const uint playerGuid = 0x50000045u;
const uint remoteGuid = 0x70000045u;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
oldId,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity player = Entity(10, playerGuid);
WorldEntity remote = Entity(11, remoteGuid);
state.MarkPersistent(playerGuid);
state.PlaceLiveEntityProjection(oldId, player);
state.PlaceLiveEntityProjection(oldId, remote);
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x45, 0x45));
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected live-owner retry");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x46, 0x46, isSealedDungeon: false));
Assert.Same(player, Assert.Single(state.DrainRescued()));
Assert.Equal(1, state.PendingLiveEntityCount);
Assert.Empty(state.Entities);
failRetirement = false;
Assert.True(recenter.Advance());
Assert.Empty(state.DrainRescued());
state.AddLandblock(new LoadedLandblock(
destinationId,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.RebucketLiveEntity(player, destinationId);
Assert.Same(player, Assert.Single(state.Entities));
state.AddLandblock(new LoadedLandblock(
oldId,
new LandBlock(),
Array.Empty<WorldEntity>()));
Assert.Contains(state.Entities, entity => ReferenceEquals(entity, remote));
state.ForgetLiveEntity(playerGuid);
WorldEntity replacement = Entity(12, playerGuid);
state.MarkPersistent(playerGuid);
state.PlaceLiveEntityProjection(destinationId, replacement);
state.RemoveLandblock(destinationId);
Assert.Same(replacement, Assert.Single(state.DrainRescued()));
Assert.DoesNotContain(state.Entities, entity => ReferenceEquals(entity, player));
}
[Fact]
public void OriginRecenter_ResetWithoutReplacementConvergesAtCurrentOrigin()
{
const uint landblockId = 0x5050FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblockId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x50, 0x50));
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected retirement failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x60, 0x60, isSealedDungeon: false));
recenter.Reset();
Assert.True(recenter.IsPending);
failRetirement = false;
Assert.True(recenter.Advance());
Assert.False(recenter.IsPending);
Assert.Equal((0x50, 0x50), (origin.CenterX, origin.CenterY));
controller.Tick(0x50, 0x50);
}
[Fact]
public void OriginRecenter_ResetBackToSealedSourceKeepsRadiusZeroMode()
{
const uint sourceId = 0x5252FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x52, 0x52));
var enqueued = new List<(uint Id, LandblockStreamJobKind Kind)>();
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: (id, kind) => enqueued.Add((id, kind)),
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 2,
farRadius: 4,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected retirement failure");
});
controller.PreCollapseToDungeon(0x52, 0x52);
enqueued.Clear();
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x60, 0x60, isSealedDungeon: false));
recenter.Reset();
failRetirement = false;
Assert.True(recenter.Advance());
Assert.Equal((0x52, 0x52), (origin.CenterX, origin.CenterY));
Assert.Equal([(sourceId, LandblockStreamJobKind.LoadNear)], enqueued);
controller.Tick(0x52, 0x52, insideDungeon: true);
Assert.Single(enqueued);
}
[Fact]
public void OriginRecenter_SessionResetConvergesWithoutBootstrappingEndingWorld()
{
const uint sourceId = 0x5353FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x53, 0x53));
var enqueued = new List<uint>();
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: (id, _) => enqueued.Add(id),
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected session reset failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x54, 0x54, isSealedDungeon: true));
recenter.Reset(sessionEnding: true);
failRetirement = false;
Assert.True(recenter.Advance());
Assert.Empty(enqueued);
Assert.Equal((0x53, 0x53), (origin.CenterX, origin.CenterY));
controller.Tick(0x53, 0x53);
Assert.Equal([sourceId], enqueued);
}
[Fact]
public void OriginRecenter_SessionResetCannotOverwriteNextSessionOrigin()
{
const uint sourceId = 0x5353FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x53, 0x53));
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected session reset failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x54, 0x54, isSealedDungeon: true));
recenter.Reset(sessionEnding: true);
origin.Reset();
Assert.True(origin.TryInitialize(0x61, 0x62));
failRetirement = false;
Assert.True(recenter.Advance());
Assert.Equal((0x61, 0x62), (origin.CenterX, origin.CenterY));
}
[Fact]
public void OriginRecenter_CommittedPendingClearFailureDoesNotReplayClear()
{
var state = new GpuWorldState();
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x10, 0x10));
int clearAttempts = 0;
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
clearPendingLoads: () =>
{
clearAttempts++;
throw new StreamingMutationException(
"injected committed clear failure",
mutationCommitted: true);
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x11, 0x11, isSealedDungeon: false));
Assert.True(recenter.Advance());
Assert.Equal(1, clearAttempts);
Assert.Equal((0x11, 0x11), (origin.CenterX, origin.CenterY));
}
[Fact]
public void OriginRecenter_ReentrantClearAndRetirementDoNotRecurseOrSkipResidents()
{
const uint firstId = 0x7070FFFFu;
const uint secondId = 0x7071FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
firstId,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
secondId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x70, 0x70));
StreamingController? controller = null;
int clearCalls = 0;
var removed = new List<uint>();
controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
clearPendingLoads: () =>
{
clearCalls++;
Assert.False(controller!.IsOriginRecenterRetirementComplete());
},
removeTerrain: id =>
{
removed.Add(id);
if (id == firstId)
Assert.False(controller!.IsOriginRecenterRetirementComplete());
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.True(recenter.Begin(0x71, 0x71, isSealedDungeon: false));
Assert.Equal(1, clearCalls);
Assert.Equal([firstId, secondId], removed);
Assert.False(state.IsLoaded(firstId));
Assert.False(state.IsLoaded(secondId));
}
[Fact]
public void OriginRecenter_DefersRadiusChangeUntilDestinationCommit()
{
const uint oldId = 0x7272FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
oldId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x72, 0x72));
var enqueued = new List<uint>();
bool failRetirement = true;
var controller = new StreamingController(
enqueueLoad: (id, _) => enqueued.Add(id),
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
removeTerrain: _ =>
{
if (failRetirement)
throw new InvalidOperationException("injected radius barrier failure");
});
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x73, 0x73, isSealedDungeon: false));
controller.ReconfigureRadii(nearRadius: 1, farRadius: 2);
controller.Tick(0x72, 0x72);
Assert.Equal(0, controller.NearRadius);
Assert.Equal(0, controller.FarRadius);
Assert.Empty(enqueued);
failRetirement = false;
Assert.True(recenter.Advance());
controller.Tick(0x73, 0x73);
Assert.Equal(1, controller.NearRadius);
Assert.Equal(2, controller.FarRadius);
Assert.Equal(25, enqueued.Count);
}
[Fact]
public void OriginRecenter_SealedDungeonPinsRadiusZeroBeforeCenterCompletion()
{
const uint oldId = 0x2020FFFFu;
const uint dungeonId = 0x3030FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
oldId,
new LandBlock(),
Array.Empty<WorldEntity>()));
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x20, 0x20));
var outbox = new Queue<LandblockStreamResult>();
var enqueued = new List<(uint Id, LandblockStreamJobKind Kind)>();
bool failDungeonEnqueue = true;
int enqueueAttempts = 0;
int publications = 0;
var controller = new StreamingController(
enqueueLoad: (id, kind) =>
{
enqueueAttempts++;
if (failDungeonEnqueue)
throw new InvalidOperationException("injected dungeon enqueue failure");
enqueued.Add((id, kind));
},
enqueueUnload: static _ => { },
drainCompletions: max => Drain(outbox, max),
applyTerrain: (_, _) => publications++,
state,
nearRadius: 2,
farRadius: 4);
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x30, 0x30, isSealedDungeon: true));
Assert.True(recenter.IsPending);
controller.Tick(0x30, 0x30, insideDungeon: true);
Assert.Empty(enqueued);
failDungeonEnqueue = false;
Assert.True(recenter.Advance());
Assert.Equal(2, enqueueAttempts);
Assert.Equal([(dungeonId, LandblockStreamJobKind.LoadNear)], enqueued);
outbox.Enqueue(new LandblockStreamResult.Loaded(
dungeonId,
LandblockStreamTier.Near,
new LoadedLandblock(
dungeonId,
new LandBlock(),
Array.Empty<WorldEntity>()),
EmptyMesh(),
generation: 1));
controller.Tick(0x30, 0x30, insideDungeon: true);
Assert.Equal(1, publications);
Assert.True(state.IsNearTier(dungeonId));
}
[Fact]
@ -715,9 +1297,10 @@ public sealed class LandblockPresentationPipelineTests
envCells);
}
private static WorldEntity Entity(uint id) => new()
private static WorldEntity Entity(uint id, uint serverGuid = 0) => new()
{
Id = id,
ServerGuid = serverGuid,
SourceGfxObjOrSetupId = 0x01000000u + id,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,