fix(streaming): preserve portal destination ownership

Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
This commit is contained in:
Erik 2026-07-25 08:35:12 +02:00
parent 2c848d4167
commit 823936ec31
57 changed files with 2551 additions and 324 deletions

View file

@ -101,7 +101,7 @@ public sealed class LandblockPresentationPipelineTests
}
[Fact]
public void OriginRecenter_BlocksOriginAndBootstrapUntilEveryOldOwnerConverges()
public void OriginRecenter_CommitsAfterDetachWhileOldOwnerCleanupContinues()
{
const uint centerId = 0x1919FFFFu;
const uint neighborId = 0x191AFFFFu;
@ -154,11 +154,63 @@ public sealed class LandblockPresentationPipelineTests
Assert.True(Converge(recenter, controller, 0x19, 0x19));
Assert.Equal((0x22, 0x23), (origin.CenterX, origin.CenterY));
Assert.Equal(0, controller.PendingRetirementCount);
Assert.Equal(1, controller.PendingRetirementCount);
controller.Tick(0x22, 0x23);
Assert.Equal(0, controller.PendingRetirementCount);
Assert.Equal([0x2223FFFFu], enqueued);
}
[Fact]
public void OriginRecenter_DetachesFullTwentyFiveByTwentyFiveWindowAtomically()
{
const int radius = 12;
const int side = radius * 2 + 1;
const int residentCount = side * side;
var state = new GpuWorldState();
for (int x = 0x40 - radius; x <= 0x40 + radius; x++)
for (int y = 0x40 - radius; y <= 0x40 + radius; y++)
{
uint id = ((uint)x << 24) | ((uint)y << 16) | 0xFFFFu;
state.AddLandblock(new LoadedLandblock(
id,
new LandBlock(),
Array.Empty<WorldEntity>()));
}
var origin = new LiveWorldOriginState();
Assert.True(origin.TryInitialize(0x40, 0x40));
var controller = new StreamingController(
enqueueLoad: static (_, _) => { },
enqueueUnload: static _ => { },
drainCompletions: static _ => Array.Empty<LandblockStreamResult>(),
applyTerrain: static (_, _) => { },
state,
nearRadius: 0,
farRadius: 0,
workBudgetOptions: new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: 1,
MaxAdoptedCpuBytes: 1,
MaxEntityOperations: 1,
MaxGpuUploadBytes: 1,
MaxGlRetireOperations: 1,
DestinationReserveFraction: 0.5f));
var recenter = new StreamingOriginRecenterCoordinator(controller, origin);
Assert.False(recenter.Begin(0x50, 0x50, isSealedDungeon: false));
int frames = 0;
while (!recenter.Advance() && frames < 8)
{
controller.Tick(0x40, 0x40);
frames++;
}
Assert.InRange(frames, 1, 4);
Assert.Empty(state.LoadedLandblockIds);
Assert.Equal(residentCount, controller.LastFullWindowRetirementLandblockCount);
Assert.Equal((0x50, 0x50), (origin.CenterX, origin.CenterY));
}
[Fact]
public void OriginRecenter_PreparationFailureBeforeDetachRetainsOldOriginAndResidents()
{