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

@ -91,7 +91,21 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
&& tracked.Projection.ProjectionClass is
RenderProjectionClass.EquippedChild)
{
Remove(tracked);
if (_runtime.IsCurrentRecord(tracked.Record)
&& ReferenceEquals(
tracked.Record.WorldEntity,
tracked.Entity))
{
// Leaving cell presentation is not logical destruction. Keep
// the projection identity inactive so a later residency or
// destination-pose edge can reactivate the same attachment
// without replaying EntityReady.
Upsert(tracked.Record, tracked.Entity, spatiallyVisible: false);
}
else
{
Remove(tracked);
}
}
}

View file

@ -323,7 +323,11 @@ internal sealed class RenderSceneShadowComparisonController :
if (!query.TryGet(id, out RenderProjectionRecord actual))
{
return $"sourceChannel={Channel(id)} id={id} field=presence "
+ "expected=present actual=missing";
+ "expected=present actual=missing "
+ $"expectedClass={fingerprint.ProjectionClass} "
+ $"serverGuid=0x{fingerprint.ServerGuid:X8} "
+ $"localEntityId=0x{fingerprint.EntityId:X8} "
+ $"parentCell=0x{fingerprint.ParentCellId:X8}";
}
string? mismatch = CompareProjection(