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

@ -59,37 +59,100 @@ public sealed class PaperdollFramePresenterTests
presenter.Render();
Assert.Equal(2, factory.BuildCount);
Assert.Equal(2, renderer.Dolls.Count);
Assert.Single(renderer.Dolls);
Assert.Equal(3, renderer.RenderCount);
Assert.False(presenter.IsDirty);
}
private static WorldEntity CreateDoll() => new()
[Fact]
public void EquivalentPortalRefresh_KeepsPrivateDollAndTextureOwner()
{
WorldEntity first = CreateDoll();
WorldEntity repeated = CreateDoll();
var renderer = new RecordingRenderer();
var view = new RecordingView();
var factory = new RecordingFactory { Doll = first };
var presenter = new PaperdollFramePresenter(renderer, view, factory);
presenter.Render();
factory.Doll = repeated;
presenter.MarkDirty();
presenter.Render();
Assert.Equal(2, factory.BuildCount);
Assert.Equal([first], renderer.Dolls);
Assert.Equal(2, renderer.RenderCount);
Assert.False(presenter.IsDirty);
}
[Fact]
public void ChangedScale_ReplacesPrivateDoll()
{
WorldEntity first = CreateDoll();
WorldEntity changed = CreateDoll(scale: 1.25f);
var renderer = new RecordingRenderer();
var view = new RecordingView();
var factory = new RecordingFactory { Doll = first };
var presenter = new PaperdollFramePresenter(renderer, view, factory);
presenter.Render();
factory.Doll = changed;
presenter.MarkDirty();
presenter.Render();
Assert.Equal([first, changed], renderer.Dolls);
}
private static WorldEntity CreateDoll(float scale = 1f) => new()
{
Id = 42u,
SourceGfxObjOrSetupId = 0x02000001u,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
Scale = scale,
};
[Fact]
public void MissingPlayer_ClearsOldDollPublishesEmptyTextureAndRetries()
public void TemporaryMissingPlayer_KeepsSuccessfulDollAndRetriesRedress()
{
var renderer = new RecordingRenderer { TextureHandle = 0u };
var firstDoll = CreateDoll();
var renderer = new RecordingRenderer { TextureHandle = 81u };
var view = new RecordingView();
var factory = new RecordingFactory { CanBuild = false };
var factory = new RecordingFactory { Doll = firstDoll };
var presenter = new PaperdollFramePresenter(renderer, view, factory);
presenter.Render();
factory.CanBuild = false;
presenter.MarkDirty();
presenter.Render();
presenter.Render();
Assert.True(presenter.IsDirty);
Assert.Equal(3, factory.BuildCount);
Assert.Equal([firstDoll], renderer.Dolls);
Assert.Equal(3, renderer.RenderCount);
Assert.Equal([81u, 81u, 81u], view.TextureHandles);
}
[Fact]
public void ResetSession_ClearsPrivateDollOnceAndArmsNextCharacterBuild()
{
var firstDoll = CreateDoll();
var secondDoll = CreateDoll();
var renderer = new RecordingRenderer();
var view = new RecordingView();
var factory = new RecordingFactory { Doll = firstDoll };
var presenter = new PaperdollFramePresenter(renderer, view, factory);
presenter.Render();
presenter.ResetSession();
factory.Doll = secondDoll;
presenter.Render();
Assert.False(presenter.IsDirty);
Assert.Equal(2, factory.BuildCount);
Assert.Equal(2, renderer.Dolls.Count);
Assert.All(renderer.Dolls, Assert.Null);
Assert.Equal(2, renderer.RenderCount);
Assert.Equal([0u, 0u], view.TextureHandles);
Assert.Equal([firstDoll, null, secondDoll], renderer.Dolls);
}
[Fact]
@ -222,8 +285,8 @@ public sealed class PaperdollFramePresenterTests
private sealed class RecordingFactory : IPaperdollDollFactory
{
public bool CanBuild { get; init; } = true;
public WorldEntity? Doll { get; init; }
public bool CanBuild { get; set; } = true;
public WorldEntity? Doll { get; set; }
public int BuildCount { get; private set; }
public bool TryBuild(out WorldEntity? doll)