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

@ -33,10 +33,9 @@ public sealed class LiveEntityCollisionBuilderTests
scale: 2f,
itemType: (uint)ItemType.Creature,
descriptionFlags: 0x28u);
var record = new LiveEntityRecord(spawn)
{
FinalPhysicsState = PhysicsStateFlags.Hidden | PhysicsStateFlags.Static,
};
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
record.FinalPhysicsState =
PhysicsStateFlags.Hidden | PhysicsStateFlags.Static;
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = Builder();
@ -65,7 +64,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(part);
WorldSession.EntitySpawn spawn = Spawn(scale: 1.5f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -90,7 +89,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(basePart);
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -115,7 +114,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(basePart);
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -145,7 +144,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(basePart);
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -178,7 +177,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(basePart);
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -243,7 +242,7 @@ public sealed class LiveEntityCollisionBuilderTests
var setup = new Setup();
setup.Parts.Add(basePart);
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
var builder = new LiveEntityCollisionBuilder(
@ -277,7 +276,7 @@ public sealed class LiveEntityCollisionBuilderTests
public void ShapelessSetup_ProducesNoRegistration()
{
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var record = new LiveEntityRecord(spawn);
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
WorldEntity entity = Entity();
record.WorldEntity = entity;
@ -294,8 +293,9 @@ public sealed class LiveEntityCollisionBuilderTests
public void Build_RejectsDifferentRecordIncarnation()
{
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
var expected = new LiveEntityRecord(spawn);
var other = new LiveEntityRecord(spawn with { InstanceSequence = 2 });
var expected = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
var other = LiveEntityTestFixture.CreateExactProjectionRecord(
spawn with { InstanceSequence = 2 });
WorldEntity entity = Entity();
expected.WorldEntity = entity;
other.WorldEntity = entity;

View file

@ -17,7 +17,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Motion_StrictFreshnessAndWrapAreOwnedBeforePresentation()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 7, movement: 0xFFFE));
Register(runtime, Spawn(Guid, instance: 7, movement: 0xFFFE));
var published = new List<AcceptedPhysicsTimestamps>();
var gate = new LiveEntityInboundAuthorityGate(
runtime,
@ -52,7 +52,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void AutonomousLocalMotion_ConsumesTimestampButDoesNotPublishPayload()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 1));
Register(runtime, Spawn(Guid, instance: 1));
int publishCount = 0;
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => publishCount++);
@ -72,7 +72,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Vector_InvalidPayloadDoesNotConsumeSequenceAndDuplicateIsRejected()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 2));
Register(runtime, Spawn(Guid, instance: 2));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
var update = new VectorUpdate.Parsed(
Guid,
@ -94,7 +94,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Vector_WrappedSequenceIsAccepted()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 2, vector: 0xFFFE));
Register(runtime, Spawn(Guid, instance: 2, vector: 0xFFFE));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
Assert.True(gate.TryAcceptVector(
@ -112,7 +112,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void State_EqualIsRejectedAndWrappedSequenceIsAccepted()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 3, state: 0xFFFE));
Register(runtime, Spawn(Guid, instance: 3, state: 0xFFFE));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
Assert.False(gate.TryAcceptState(
@ -149,7 +149,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
Assert.Equal(0xAABB0001u, gate.LastLivePlayerLandblockId);
Assert.Equal(0, publishCount);
runtime.RegisterLiveEntity(Spawn(Guid, instance: 4, position: 1));
Register(runtime, Spawn(Guid, instance: 4, position: 1));
Assert.True(gate.TryAcceptPosition(
update,
Guid,
@ -169,7 +169,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Position_InvalidPayloadDoesNotConsumeSequence()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, position: 1));
Register(runtime, Spawn(Guid, instance: 5, position: 1));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
WorldSession.EntityPositionUpdate update = Position(Guid, 5, 2, 0x01010001u);
@ -185,7 +185,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Position_WrappedSequenceIsAccepted()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, position: 0xFFFE));
Register(runtime, Spawn(Guid, instance: 5, position: 0xFFFE));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
Assert.True(gate.TryAcceptPosition(
@ -201,7 +201,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Vector_NewerSameIncarnationPacketInvalidatesCapturedAuthority()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, vector: 1));
Register(runtime, Spawn(Guid, instance: 5, vector: 1));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
Assert.True(gate.TryAcceptVector(
new VectorUpdate.Parsed(Guid, Vector3.One, Vector3.Zero, 5, 2),
@ -220,7 +220,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void State_NewerSameIncarnationPacketInvalidatesCapturedAuthority()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, state: 1));
Register(runtime, Spawn(Guid, instance: 5, state: 1));
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
Assert.True(gate.TryAcceptState(
new SetState.Parsed(Guid, (uint)PhysicsStateFlags.Hidden, 5, 2),
@ -238,7 +238,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Motion_PublisherReplacementInvalidatesCapturedIncarnation()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 6));
Register(runtime, Spawn(Guid, instance: 6));
var gate = new LiveEntityInboundAuthorityGate(
runtime,
(_, _) => runtime.RegisterLiveEntity(Spawn(Guid, instance: 7)));
@ -249,15 +249,17 @@ public sealed class LiveEntityInboundAuthorityGateTests
out _,
out bool timestampAccepted));
Assert.True(timestampAccepted);
Assert.True(runtime.TryGetRecord(Guid, out LiveEntityRecord replacement));
Assert.Equal((ushort)7, replacement.Generation);
Assert.True(runtime.TryGetCanonical(Guid, out RuntimeEntityRecord replacement));
Assert.Equal((ushort)7, replacement.Incarnation);
Assert.Null(replacement.LocalEntityId);
Assert.False(runtime.TryGetRecord(Guid, out _));
}
[Fact]
public void Position_PublisherNewerChannelInvalidatesCapturedAuthority()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 8, position: 1));
Register(runtime, Spawn(Guid, instance: 8, position: 1));
var nested = Position(Guid, 8, 3, 0x01010002u);
LiveEntityInboundAuthorityGate? gate = null;
gate = new LiveEntityInboundAuthorityGate(
@ -286,7 +288,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
public void Position_PublisherNewerVectorPreservesIndependentPositionAuthority()
{
LiveEntityRuntime runtime = Runtime();
runtime.RegisterLiveEntity(Spawn(Guid, instance: 9, position: 1, vector: 1));
Register(runtime, Spawn(Guid, instance: 9, position: 1, vector: 1));
var gate = new LiveEntityInboundAuthorityGate(
runtime,
(_, _) => runtime.TryApplyVector(
@ -317,6 +319,11 @@ public sealed class LiveEntityInboundAuthorityGateTests
new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
private static LiveEntityRecord Register(
LiveEntityRuntime runtime,
WorldSession.EntitySpawn spawn) =>
runtime.RegisterAndMaterializeProjection(spawn);
private static WorldSession.EntityMotionUpdate Motion(
ushort instance,
ushort movement,

View file

@ -26,10 +26,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
var live = new LiveEntityRuntime(
spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterLiveEntity(Spawn()).Record!;
WorldEntity entity = live.MaterializeLiveEntity(
Guid,
SourceCell,
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
Spawn(),
id => new WorldEntity
{
Id = id,
@ -39,7 +37,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
ParentCellId = SourceCell,
})!;
});
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
var remote = new AcDream.App.Physics.RemoteMotion
{
@ -94,10 +93,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
var live = new LiveEntityRuntime(
spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterLiveEntity(Spawn()).Record!;
WorldEntity entity = live.MaterializeLiveEntity(
Guid,
SourceCell,
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
Spawn(),
id => new WorldEntity
{
Id = id,
@ -107,7 +104,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
ParentCellId = SourceCell,
})!;
});
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = SourceCell,

View file

@ -243,18 +243,18 @@ public sealed class ProjectileControllerTests
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
Assert.True(record.PhysicsBody!.InWorld);
Assert.True(record.PhysicsBody.IsActive);
Assert.Single(fixture.Live.SpatialProjectileRuntimes);
Assert.Equal(1, fixture.Live.SpatialProjectileRuntimeCount);
fixture.Spatial.RemoveLandblock(0x0101FFFFu);
Assert.False(record.IsSpatiallyVisible);
Assert.Empty(fixture.Live.SpatialProjectileRuntimes);
Assert.Equal(0, fixture.Live.SpatialProjectileRuntimeCount);
Assert.False(record.PhysicsBody.InWorld);
Assert.False(record.PhysicsBody.IsActive);
Assert.Equal(0, fixture.Engine.ShadowObjects.TotalRegistered);
fixture.Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
Assert.Single(fixture.Live.SpatialProjectileRuntimes);
Assert.Equal(1, fixture.Live.SpatialProjectileRuntimeCount);
Assert.True(record.PhysicsBody.InWorld);
Assert.True(record.PhysicsBody.IsActive);
Assert.Equal(1, fixture.Engine.ShadowObjects.TotalRegistered);
@ -545,7 +545,8 @@ public sealed class ProjectileControllerTests
Assert.Same(runtime, record.ProjectileRuntime);
Assert.Same(body, record.PhysicsBody);
Assert.True(body.IsActive); // State preserves an already-active body.
Assert.Same(record.WorldEntity, fixture.Live.MaterializedWorldEntities[Guid]);
Assert.True(fixture.Live.TryGetWorldEntity(Guid, out WorldEntity entity));
Assert.Same(record.WorldEntity, entity);
Assert.Equal(1, fixture.Resources.Registers);
}
@ -1501,12 +1502,8 @@ public sealed class ProjectileControllerTests
angularVelocity ?? Vector3.Zero,
friction,
elasticity);
LiveEntityRegistrationResult registration = Live.RegisterLiveEntity(spawn);
Assert.NotNull(registration.Record);
registration.Record!.HasPartArray = true;
WorldEntity? entity = Live.MaterializeLiveEntity(
Guid,
cellId,
LiveEntityRecord record = Live.RegisterAndMaterializeProjection(
spawn,
localId => new WorldEntity
{
Id = localId,
@ -1518,10 +1515,11 @@ public sealed class ProjectileControllerTests
? new[] { new MeshRef(0x01000001u, Matrix4x4.Identity) }
: Array.Empty<MeshRef>(),
ParentCellId = cellId,
});
Assert.NotNull(entity);
},
initializeProjection: exact => exact.HasPartArray = true);
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
Poses.PublishMeshRefs(entity);
return registration.Record!;
return record;
}
}

View file

@ -697,7 +697,7 @@ public sealed class RemotePhysicsUpdaterTests
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
BindHiddenRemote(live, firstGuid);
BindHiddenRemote(live, secondGuid);
Assert.Equal(2, live.SpatialRemoteMotionRuntimes.Count);
Assert.Equal(2, live.SpatialRemoteMotionRuntimeCount);
var updater = new RemotePhysicsUpdater(
new PhysicsEngine(),
@ -721,7 +721,7 @@ public sealed class RemotePhysicsUpdaterTests
Assert.Single(published);
Assert.Equal(published, partPoseDirty);
Assert.Single(live.SpatialRemoteMotionRuntimes);
Assert.Equal(1, live.SpatialRemoteMotionRuntimeCount);
}
private static PhysicsEngine BuildBoundaryEngine()
@ -908,14 +908,8 @@ public sealed class RemotePhysicsUpdaterTests
Vector3 position,
bool enqueueDestination)
{
LiveEntityRegistrationResult registration = Live.RegisterLiveEntity(
Spawn(instanceSequence, position));
LiveEntityRecord record = Assert.IsType<LiveEntityRecord>(
registration.Record);
WorldEntity entity = Assert.IsType<WorldEntity>(
Live.MaterializeLiveEntity(
Guid,
SourceCell,
LiveEntityRecord record = Live.RegisterAndMaterializeProjection(
Spawn(instanceSequence, position),
id => new WorldEntity
{
Id = id,
@ -925,7 +919,8 @@ public sealed class RemotePhysicsUpdaterTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
ParentCellId = SourceCell,
}));
});
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.Position = position;
remote.Body.Orientation = Quaternion.Identity;

View file

@ -1089,13 +1089,14 @@ public sealed class RemoteTeleportControllerTests
const uint sourceCell = 0xA9B40039u;
const uint destinationCell = 0xAAB40001u;
RemoteTeleportController? controller = null;
LiveEntityRecord? owner = null;
bool cleanupAfterFailureRan = false;
var resources = new DelegateLiveEntityResourceLifecycle(
_ => { },
_ => LiveEntityTeardown.Run(
[
() => throw new InvalidOperationException("effect teardown failed"),
() => controller!.Forget(guid),
() => controller!.Forget(owner!),
() => cleanupAfterFailureRan = true,
]));
var spatial = new GpuWorldState();
@ -1117,6 +1118,7 @@ public sealed class RemoteTeleportControllerTests
var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(sourceCell, entity.Position, entity.Position);
live.SetRemoteMotionRuntime(guid, remote);
Assert.True(live.TryGetRecord(guid, out owner));
controller = new RemoteTeleportController(
BuildEngine(includeDestination: false),
live,