refactor(app): key live projections by runtime identity

Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
This commit is contained in:
Erik 2026-07-25 21:50:58 +02:00
parent e18df84437
commit 420e5eea70
73 changed files with 2939 additions and 1715 deletions

View file

@ -227,14 +227,15 @@ public sealed class GpuWorldStateVisibilityTests
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73600001u);
state.PlaceLiveEntityProjection(landblock, entity);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
var edges = new List<(uint LocalEntityId, bool Visible)>();
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
edges.Add((localEntityId, visible));
state.RemoveLandblock(landblock);
Assert.Empty(state.Entities);
Assert.Equal(1, state.PendingLiveEntityCount);
Assert.False(state.IsLiveEntityVisible(entity.ServerGuid));
Assert.False(state.IsLiveEntityVisible(entity.Id));
state.AddLandblock(new LoadedLandblock(
landblock,
@ -244,9 +245,9 @@ public sealed class GpuWorldStateVisibilityTests
Assert.Same(entity, Assert.Single(state.Entities));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? reloaded));
Assert.Same(entity, Assert.Single(reloaded!.Entities));
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
Assert.True(state.IsLiveEntityVisible(entity.Id));
Assert.Equal(
[(entity.ServerGuid, false), (entity.ServerGuid, true)],
[(entity.Id, false), (entity.Id, true)],
edges);
state.RemoveLiveEntityProjection(entity);
@ -279,9 +280,9 @@ public sealed class GpuWorldStateVisibilityTests
state.MarkPersistent(playerGuid);
state.PlaceLiveEntityProjection(secondLandblock, player);
state.PlaceLiveEntityProjection(pendingLandblock, pending);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (guid, visible) =>
edges.Add((guid, visible));
var edges = new List<(uint LocalEntityId, bool Visible)>();
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
edges.Add((localEntityId, visible));
GpuWorldRecenterRetirement result =
state.DetachAllForOriginRecenter();
@ -316,7 +317,7 @@ public sealed class GpuWorldStateVisibilityTests
Assert.Equal(2, state.PendingLiveEntityCount);
Assert.Same(player, Assert.Single(state.DrainRescued()));
Assert.Equal(
[(remote.ServerGuid, false), (player.ServerGuid, false)],
[(remote.Id, false), (player.Id, false)],
edges);
state.AddLandblock(new LoadedLandblock(
@ -378,12 +379,12 @@ public sealed class GpuWorldStateVisibilityTests
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73700001u);
int callbacks = 0;
state.LiveProjectionVisibilityChanged += (guid, visible) =>
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
{
callbacks++;
Assert.Equal(entity.ServerGuid, guid);
Assert.Equal(entity.Id, localEntityId);
Assert.True(visible);
Assert.True(state.IsLiveEntityVisible(guid));
Assert.True(state.IsLiveEntityVisible(localEntityId));
Assert.Same(entity, Assert.Single(state.Entities));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? loaded));
Assert.Same(entity, Assert.Single(loaded!.Entities));
@ -414,13 +415,14 @@ public sealed class GpuWorldStateVisibilityTests
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73800001u);
state.PlaceLiveEntityProjection(sourceLandblock, entity);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
var edges = new List<(uint LocalEntityId, bool Visible)>();
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
edges.Add((localEntityId, visible));
state.RebucketLiveEntity(entity, targetLandblock);
Assert.Empty(edges);
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
Assert.True(state.IsLiveEntityVisible(entity.Id));
Assert.True(state.TryGetLandblock(sourceLandblock, out LoadedLandblock? source));
Assert.Empty(source!.Entities);
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
@ -429,7 +431,7 @@ public sealed class GpuWorldStateVisibilityTests
}
[Fact]
public void SameGuidOverlap_ExactRemovalPromotesSecondaryWithoutVisibilityPulse()
public void SameGuidOverlap_TracksExactProjectionVisibilityIndependently()
{
const uint landblock = 0x0101FFFFu;
const uint guid = 0x73900001u;
@ -442,20 +444,21 @@ public sealed class GpuWorldStateVisibilityTests
WorldEntity second = Entity(2u, guid);
state.PlaceLiveEntityProjection(landblock, first);
state.PlaceLiveEntityProjection(landblock, second);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (edgeGuid, visible) =>
edges.Add((edgeGuid, visible));
var edges = new List<(uint LocalEntityId, bool Visible)>();
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
edges.Add((localEntityId, visible));
state.RemoveLiveEntityProjection(first);
Assert.Empty(edges);
Assert.True(state.IsLiveEntityVisible(guid));
Assert.Equal([(first.Id, false)], edges);
Assert.False(state.IsLiveEntityVisible(first.Id));
Assert.True(state.IsLiveEntityVisible(second.Id));
Assert.Same(second, Assert.Single(state.Entities));
state.RemoveLiveEntityProjection(guid);
Assert.Equal([(guid, false)], edges);
Assert.False(state.IsLiveEntityVisible(guid));
Assert.Equal([(first.Id, false), (second.Id, false)], edges);
Assert.False(state.IsLiveEntityVisible(second.Id));
Assert.Empty(state.Entities);
}
@ -488,7 +491,7 @@ public sealed class GpuWorldStateVisibilityTests
var state = new GpuWorldState();
state.PlaceLiveEntityProjection(landblock, first);
state.PlaceLiveEntityProjection(landblock, second);
var observed = new List<(uint Guid, bool Visible)>();
var observed = new List<(uint LocalEntityId, bool Visible)>();
state.LiveProjectionVisibilityChanged += (_, _) =>
throw new InvalidOperationException("fixture observer failure");
state.LiveProjectionVisibilityChanged += (guid, visible) =>
@ -502,10 +505,10 @@ public sealed class GpuWorldStateVisibilityTests
Assert.Equal(2, error.InnerExceptions.Count);
Assert.Equal(
[(first.ServerGuid, true), (second.ServerGuid, true)],
observed.OrderBy(edge => edge.Guid).ToArray());
Assert.True(state.IsLiveEntityVisible(first.ServerGuid));
Assert.True(state.IsLiveEntityVisible(second.ServerGuid));
[(first.Id, true), (second.Id, true)],
observed.OrderBy(edge => edge.LocalEntityId).ToArray());
Assert.True(state.IsLiveEntityVisible(first.Id));
Assert.True(state.IsLiveEntityVisible(second.Id));
Assert.Equal(0, state.PendingVisibilityTransitionCount);
Assert.Equal(2, state.Entities.Count);
}
@ -574,14 +577,14 @@ public sealed class GpuWorldStateVisibilityTests
cell,
id => Entity(id, guid));
Assert.False(state.IsLiveEntityVisible(guid));
Assert.True(state.IsLiveEntityProjectionResident(guid));
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
Assert.False(state.IsLiveEntityVisible(record.WorldEntity!.Id));
Assert.True(state.IsLiveEntityProjectionResident(record.WorldEntity.Id));
Assert.True(record.IsSpatiallyVisible);
Assert.Equal([true], edges);
Assert.True(availability.End(7));
Assert.True(state.IsLiveEntityVisible(guid));
Assert.True(state.IsLiveEntityVisible(record.WorldEntity.Id));
Assert.True(record.IsSpatiallyVisible);
}

View file

@ -26,7 +26,7 @@ public sealed class WorldGenerationQuiescenceTests
world.SetLandblockAabb(LandblockId, Vector3.Zero, Vector3.One);
var nearby = new List<KeyValuePair<uint, WorldEntity>>();
Assert.True(world.IsLiveEntityVisible(ServerGuid));
Assert.True(world.IsLiveEntityVisible(entity.Id));
Assert.Single(world.LandblockEntries);
Assert.Single(world.LandblockBounds);
@ -34,7 +34,7 @@ public sealed class WorldGenerationQuiescenceTests
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
Assert.False(availability.IsWorldAvailable);
Assert.False(world.IsLiveEntityVisible(ServerGuid));
Assert.False(world.IsLiveEntityVisible(entity.Id));
Assert.Empty(world.LandblockEntries);
Assert.Empty(world.LandblockBounds);
Assert.Empty(nearby);
@ -44,7 +44,7 @@ public sealed class WorldGenerationQuiescenceTests
Assert.False(availability.End(16));
Assert.False(availability.IsWorldAvailable);
Assert.True(availability.End(17));
Assert.True(world.IsLiveEntityVisible(ServerGuid));
Assert.True(world.IsLiveEntityVisible(entity.Id));
Assert.Same(entity, Assert.Single(world.LandblockEntries).Entities.Single());
}
@ -57,7 +57,8 @@ public sealed class WorldGenerationQuiescenceTests
LandblockId,
new LandBlock(),
Array.Empty<WorldEntity>()));
world.PlaceLiveEntityProjection(LandblockId, Entity());
WorldEntity entity = Entity();
world.PlaceLiveEntityProjection(LandblockId, entity);
var selection = new SelectionState();
selection.Select(ServerGuid, SelectionChangeSource.World);
var audio = new RecordingAudioQuiescence();
@ -65,6 +66,7 @@ public sealed class WorldGenerationQuiescenceTests
availability,
selection,
world,
guid => guid == ServerGuid ? entity.Id : null,
audio);
quiescence.Begin(1);
@ -95,6 +97,7 @@ public sealed class WorldGenerationQuiescenceTests
availability,
selection,
world,
_ => null,
audio: null);
quiescence.Begin(3);

View file

@ -132,6 +132,7 @@ public sealed class WorldRevealCoordinatorTests
availability,
new SelectionState(),
world,
_ => null,
audio);
var coordinator = new WorldRevealCoordinator(
isRenderNeighborhoodReady: (_, _) => true,
@ -165,6 +166,7 @@ public sealed class WorldRevealCoordinatorTests
availability,
new SelectionState(),
world,
_ => null,
audio);
var scheduler = new RecordingDestinationScheduler();
var coordinator = new WorldRevealCoordinator(
@ -215,6 +217,7 @@ public sealed class WorldRevealCoordinatorTests
availability,
new SelectionState(),
world,
_ => null,
audio: null);
var scheduler = new RecordingDestinationScheduler();
var coordinator = new WorldRevealCoordinator(