fix(rendering): retain withdrawn attachment identities

Separate attached-child spatial withdrawal from logical teardown. ProjectionRemoved now deactivates the retained scene identity, allowing residency or destination-pose callbacks to reactivate it; resource unregister remains the sole destruction edge.

Release gate: 3,735 App tests / 3 skips; 8,219 complete-solution tests / 5 skips.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-24 23:26:30 +02:00
parent bb1f4a64bf
commit 81e2f1a575
4 changed files with 81 additions and 12 deletions

View file

@ -140,13 +140,14 @@ public sealed class LiveRenderProjectionJournalTests
Assert.Equal(
incarnation,
harness.Journal.Pending[0].Record.OwnerIncarnation);
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
harness.Projections.OnProjectionRemoved(record.WorldEntity.Id);
harness.Projections.OnProjectionRemoved(record.WorldEntity.Id);
Assert.Equal(2, harness.Journal.Count);
Assert.Equal(
RenderProjectionDeltaKind.Unregister,
RenderProjectionDeltaKind.UpdateFlags,
harness.Journal.Pending[1].Kind);
Assert.Equal(0, harness.Projections.ProjectionCount);
Assert.Equal(1, harness.Projections.ProjectionCount);
}
[Fact]
@ -266,7 +267,7 @@ public sealed class LiveRenderProjectionJournalTests
}
[Fact]
public void SynchronizeActiveSources_DoesNotResurrectRemovedAttachment()
public void SynchronizeActiveSources_DoesNotReactivateWithdrawnAttachment()
{
Harness harness = CreateHarness(loadLandblock: true);
LiveEntityRecord record = Materialize(
@ -277,13 +278,18 @@ public sealed class LiveRenderProjectionJournalTests
Assert.True(harness.Projections.OnEntityReady(
LiveEntityReadyCandidate.Capture(record)));
harness.Journal.DrainTo(harness.Scene);
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
harness.Journal.DrainTo(harness.Scene);
harness.Projections.SynchronizeActiveSources();
Assert.Equal(0, harness.Journal.Count);
Assert.Equal(0, harness.Projections.ProjectionCount);
Assert.Equal(1, harness.Projections.ProjectionCount);
Assert.True(harness.Scene.OpenQuery().TryGet(
LiveRenderProjectionJournal.ProjectionId(record.WorldEntity!.Id),
out RenderProjectionRecord inactive));
Assert.True((inactive.Flags & RenderProjectionFlags.Draw) == 0);
}
[Fact]
@ -298,20 +304,46 @@ public sealed class LiveRenderProjectionJournalTests
Assert.True(harness.Projections.OnEntityReady(
LiveEntityReadyCandidate.Capture(record)));
harness.Journal.DrainTo(harness.Scene);
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
harness.Journal.DrainTo(harness.Scene);
Assert.True(harness.Runtime.RebucketLiveEntity(Guid, CellId));
harness.Projections.OnProjectionPoseReady(record.ServerGuid);
RenderProjectionDelta recovered =
Assert.Single(harness.Journal.Pending.ToArray());
Assert.Equal(RenderProjectionDeltaKind.Register, recovered.Kind);
Assert.Equal(
RenderProjectionClass.EquippedChild,
recovered.Record.ProjectionClass);
RenderProjectionDelta recovered = Assert.Single(
harness.Journal.Pending.ToArray(),
delta => delta.Kind is RenderProjectionDeltaKind.UpdateFlags);
Assert.Equal(record.LocalEntityId, recovered.Record.Source.LocalEntityId);
}
[Fact]
public void WithdrawnAttachment_LogicalTeardownUnregistersRetainedIdentity()
{
Harness harness = CreateHarness(loadLandblock: true);
const ushort instance = 11;
LiveEntityRecord record = Materialize(
harness,
Guid,
instance,
projectionKind: LiveEntityProjectionKind.Attached);
Assert.True(harness.Projections.OnEntityReady(
LiveEntityReadyCandidate.Capture(record)));
harness.Journal.DrainTo(harness.Scene);
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
harness.Journal.DrainTo(harness.Scene);
Assert.True(harness.Runtime.UnregisterLiveEntity(
new DeleteObject.Parsed(Guid, instance),
isLocalPlayer: false));
RenderProjectionDelta removed =
Assert.Single(harness.Journal.Pending.ToArray());
Assert.Equal(RenderProjectionDeltaKind.Unregister, removed.Kind);
Assert.Equal(0, harness.Projections.ProjectionCount);
}
private static Harness CreateHarness(bool loadLandblock)
{
var state = new GpuWorldState();