using AcDream.App.Streaming; using AcDream.App.World; using AcDream.App.Rendering.Vfx; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.Vfx; using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; using DatPhysicsScript = DatReaderWriter.DBObjs.PhysicsScript; namespace AcDream.App.Tests.World; public sealed class LiveEntityRuntimeTests { private sealed class RecordingResources : ILiveEntityResourceLifecycle { public int RegisterCount { get; private set; } public int UnregisterCount { get; private set; } public void Register(WorldEntity entity) => RegisterCount++; public void Unregister(WorldEntity entity) => UnregisterCount++; } private sealed class AnimationRuntime(WorldEntity entity) : ILiveEntityAnimationRuntime { public WorldEntity Entity { get; } = entity; } private sealed class EffectProfile : ILiveEntityEffectProfile { } private sealed class RemoteMotionRuntime : ILiveEntityRemoteMotionRuntime { public PhysicsBody Body { get; } = new(); } private sealed class FailingRegisterResources : ILiveEntityResourceLifecycle { public int RegisterCount { get; private set; } public int UnregisterCount { get; private set; } public void Register(WorldEntity entity) { RegisterCount++; throw new InvalidOperationException("fixture registration failure"); } public void Unregister(WorldEntity entity) => UnregisterCount++; } private sealed class FailingUnregisterResources(uint failingGuid) : ILiveEntityResourceLifecycle { public int UnregisterCount { get; private set; } public void Register(WorldEntity entity) { } public void Unregister(WorldEntity entity) { UnregisterCount++; if (entity.ServerGuid == failingGuid) throw new InvalidOperationException("fixture unregister failure"); } } [Fact] public void RegisterRebucketWithdrawAndRestore_UsesOneLogicalCreate() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000001u, 1, 1, 0x01010001u); LiveEntityRegistrationResult registered = runtime.RegisterLiveEntity(spawn); WorldEntity? entity = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid)); runtime.SetAnimationRuntime(spawn.Guid, new AnimationRuntime(entity!)); var visibilityEdges = new List(); runtime.ProjectionVisibilityChanged += (_, visible) => visibilityEdges.Add(visible); Assert.True(registered.LogicalRegistrationCreated); Assert.Equal(1, resources.RegisterCount); Assert.Single(spatial.Entities); Assert.True(runtime.RebucketLiveEntity(spawn.Guid, 0x01020001u)); Assert.Equal(1, resources.RegisterCount); Assert.Single(spatial.Entities); Assert.True(runtime.WithdrawLiveEntityProjection(spawn.Guid)); Assert.Empty(spatial.Entities); Assert.Empty(runtime.WorldEntities); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); Assert.True(runtime.TryGetAnimationRuntime(entity!.Id, out _)); Assert.True(runtime.RebucketLiveEntity(spawn.Guid, 0x01010001u)); Assert.Single(spatial.Entities); Assert.Equal(1, resources.RegisterCount); Assert.True(runtime.TryGetAnimationRuntime(entity!.Id, out _)); Assert.Equal(new[] { false, true }, visibilityEdges); } [Fact] public void EffectProfile_SurvivesRebucketAndClearsWithLogicalTeardown() { const uint guid = 0x70000030u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); WorldSession.EntitySpawn spawn = Spawn(guid, 6, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); var profile = new EffectProfile(); runtime.SetEffectProfile(guid, profile); runtime.MaterializeLiveEntity( guid, spawn.Position!.Value.LandblockId, id => Entity(id, guid)); Assert.True(runtime.RebucketLiveEntity(guid, 0x01020001u)); Assert.True(runtime.TryGetEffectProfile(guid, out var retained)); Assert.Same(profile, retained); Assert.True(runtime.UnregisterLiveEntity( new DeleteObject.Parsed(guid, spawn.InstanceSequence), isLocalPlayer: false)); Assert.False(runtime.TryGetEffectProfile(guid, out _)); } [Fact] public void InitialChildCreate_PreservesParentEventQueuedForMissingChild() { const uint parentGuid = 0x70000020u; const uint childGuid = 0x70000021u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); runtime.RegisterLiveEntity(Spawn(parentGuid, 9, 1, 0x01010001u)); runtime.ParentAttachments.Enqueue(new ParentEvent.Parsed( parentGuid, childGuid, 1, 2, 9, 5)); ResolveParent(runtime, childGuid); runtime.RegisterLiveEntity(Spawn(childGuid, 3, 4, 0x01010001u)); ResolveParent(runtime, childGuid); Assert.True(runtime.ParentAttachments.TryGetProjection( childGuid, out ParentAttachmentRelation relation)); Assert.Equal(parentGuid, relation.ParentGuid); Assert.True(runtime.TryGetSnapshot(childGuid, out WorldSession.EntitySpawn child)); Assert.Null(child.Position); } [Fact] public void InitialParentCreate_PreservesCreateObjectRelationWaitingForParent() { const uint parentGuid = 0x70000022u; const uint childGuid = 0x70000023u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); runtime.RegisterLiveEntity(Spawn(childGuid, 3, 4, 0x01010001u)); runtime.ParentAttachments.AcceptCreateObjectRelation(new ParentAttachmentRelation( parentGuid, childGuid, 1, 2, 9, 4)); runtime.RegisterLiveEntity(Spawn(parentGuid, 9, 1, 0x01010001u)); Assert.True(runtime.ParentAttachments.TryGetProjection( childGuid, out ParentAttachmentRelation relation)); Assert.Equal(parentGuid, relation.ParentGuid); } [Fact] public void SameGenerationCreate_DoesNotReconstructProjection() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn first = Spawn(0x70000002u, 4, 10, 0x01010001u); runtime.RegisterLiveEntity(first); WorldEntity original = runtime.MaterializeLiveEntity( first.Guid, first.Position!.Value.LandblockId, id => Entity(id, first.Guid))!; LiveEntityRegistrationResult refresh = runtime.RegisterLiveEntity( Spawn(first.Guid, 4, 11, 0x01010001u)); WorldEntity retained = runtime.MaterializeLiveEntity( first.Guid, first.Position.Value.LandblockId, _ => throw new InvalidOperationException("same generation reconstructed"))!; Assert.False(refresh.LogicalRegistrationCreated); Assert.Same(original, retained); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); } [Fact] public void NewGeneration_TearsDownExactlyOnceAndReusesNoLocalIdentity() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); int runtimeTearDowns = 0; var runtime = new LiveEntityRuntime( spatial, resources, _ => runtimeTearDowns++); const uint guid = 0x70000003u; runtime.RegisterLiveEntity(Spawn(guid, 9, 1, 0x01010001u)); WorldEntity old = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; LiveEntityRegistrationResult replacement = runtime.RegisterLiveEntity( Spawn(guid, 10, 1, 0x01010001u)); WorldEntity current = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.True(replacement.ReplacedExistingGeneration); Assert.NotEqual(old.Id, current.Id); Assert.Equal(2, resources.RegisterCount); Assert.Equal(1, resources.UnregisterCount); Assert.Equal(1, runtimeTearDowns); Assert.Single(spatial.Entities); Assert.Same(current, spatial.Entities[0]); Assert.False(runtime.TryGetServerGuid(old.Id, out _)); Assert.True(runtime.TryGetServerGuid(current.Id, out uint mapped)); Assert.Equal(guid, mapped); } [Fact] public void AppearanceUpdate_MutatesSnapshotWithoutChangingIdentity() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000004u, 2, 3, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( spawn.Guid, 0x01010001u, id => Entity(id, spawn.Guid))!; var update = new ObjDescEvent.Parsed( spawn.Guid, new CreateObject.ModelData( 0x04000001u, Array.Empty(), Array.Empty(), Array.Empty()), InstanceSequence: 2, ObjDescSequence: 2); Assert.True(runtime.TryApplyObjDesc(update, out _)); Assert.True(runtime.TryGetRecord(spawn.Guid, out LiveEntityRecord record)); Assert.Same(entity, record.WorldEntity); Assert.Equal((uint)0x04000001u, record.Snapshot.BasePaletteId); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); } [Fact] public void DeleteAndGuidReuse_HaveSeparateLogicalLifetimes() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); const uint guid = 0x70000005u; runtime.RegisterLiveEntity(Spawn(guid, 3, 1, 0x01010001u)); WorldEntity first = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.True(runtime.UnregisterLiveEntity( new DeleteObject.Parsed(guid, 3), isLocalPlayer: false)); Assert.Empty(spatial.Entities); Assert.False(runtime.TryGetRecord(guid, out _)); runtime.RegisterLiveEntity(Spawn(guid, 3, 1, 0x01010001u)); WorldEntity second = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.NotEqual(first.Id, second.Id); Assert.Equal(2, resources.RegisterCount); Assert.Equal(1, resources.UnregisterCount); } [Fact] public void AttachedProjection_RendersWithoutEnteringTopLevelWorldIndex() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000006u, 3, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity attached = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid), LiveEntityProjectionKind.Attached)!; Assert.Single(spatial.Entities); Assert.Empty(runtime.WorldEntities); Assert.False(runtime.TryMarkWorldSpawnPublished(spawn.Guid)); Assert.True(runtime.TryGetWorldEntity(spawn.Guid, out WorldEntity resolved)); Assert.Same(attached, resolved); Assert.Equal(1, resources.RegisterCount); attached.IsAncestorDrawVisible = false; WorldEntity same = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position.Value.LandblockId, _ => throw new InvalidOperationException("attachment reconstructed"), LiveEntityProjectionKind.World)!; Assert.Same(attached, same); Assert.True(same.IsAncestorDrawVisible); Assert.Same(attached, Assert.Single(runtime.WorldEntities).Value); Assert.True(runtime.TryMarkWorldSpawnPublished(spawn.Guid)); Assert.False(runtime.TryMarkWorldSpawnPublished(spawn.Guid)); Assert.Equal(1, resources.RegisterCount); } [Fact] public void PendingToLoadedTransition_DoesNotReplayLogicalRegistration() { var spatial = new GpuWorldState(); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000007u, 1, 1, 0x01020001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid))!; Assert.Empty(spatial.Entities); Assert.Equal(1, spatial.PendingLiveEntityCount); Assert.Equal(1, resources.RegisterCount); Assert.Empty(runtime.WorldEntities); Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); Assert.Same(entity, Assert.Single(spatial.Entities)); Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value); Assert.True(runtime.RebucketLiveEntity(spawn.Guid, 0x01020001u)); Assert.Same(entity, Assert.Single(spatial.Entities)); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); } [Fact] public void PendingProjection_RemainsCanonicalAcrossAuthoritativeUpdatesWithoutReregister() { const uint guid = 0x70000025u; var spatial = new GpuWorldState(); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(guid, 2, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( guid, spawn.Position!.Value.LandblockId, id => Entity(id, guid))!; Assert.Empty(runtime.WorldEntities); Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value); Assert.True(runtime.TryApplyVector( new VectorUpdate.Parsed( guid, new System.Numerics.Vector3(2f, 3f, 4f), System.Numerics.Vector3.Zero, spawn.InstanceSequence, 2), out _)); var positionUpdate = new WorldSession.EntityPositionUpdate( guid, new CreateObject.ServerPosition( 0x01020001u, 20f, 30f, 6f, 1f, 0f, 0f, 0f), null, null, true, spawn.InstanceSequence, 2, 0, 0); Assert.True(runtime.TryApplyPosition( positionUpdate, isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out PositionTimestampDisposition disposition, out WorldSession.EntitySpawn accepted, out _)); Assert.Equal(PositionTimestampDisposition.Apply, disposition); Assert.True(runtime.RebucketLiveEntity(guid, accepted.Position!.Value.LandblockId)); Assert.Empty(runtime.WorldEntities); Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); Assert.Equal(1, spatial.PendingLiveEntityCount); } [Fact] public void PickupLeaveWorld_PreservesOwnersButPausesRootUntilPositionReentry() { const uint guid = 0x70000026u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(guid, 4, 10, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( guid, spawn.Position!.Value.LandblockId, id => Entity(id, guid))!; var animation = new AnimationRuntime(entity); runtime.SetAnimationRuntime(guid, animation); uint localId = entity.Id; Assert.True(runtime.TryMarkWorldSpawnPublished(guid)); Assert.True(runtime.TryApplyPickup( new PickupEvent.Parsed(guid, spawn.InstanceSequence, 11), out _)); Assert.True(runtime.WithdrawLiveEntityProjection(guid)); Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record)); Assert.Equal(0u, record.FullCellId); Assert.Equal(0u, record.CanonicalLandblockId); Assert.False(runtime.ShouldAdvanceRootRuntime(guid)); Assert.True(runtime.TryGetAnimationRuntime(localId, out var retainedAnimation)); Assert.Same(animation, retainedAnimation); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); var positionUpdate = new WorldSession.EntityPositionUpdate( guid, new CreateObject.ServerPosition( 0x01010002u, 30f, 40f, 7f, 1f, 0f, 0f, 0f), null, null, true, spawn.InstanceSequence, 12, 0, 0); Assert.True(runtime.TryApplyPosition( positionUpdate, isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out PositionTimestampDisposition disposition, out WorldSession.EntitySpawn accepted, out _)); Assert.Equal(PositionTimestampDisposition.Apply, disposition); WorldEntity same = runtime.MaterializeLiveEntity( guid, accepted.Position!.Value.LandblockId, _ => throw new InvalidOperationException("re-entry reconstructed the projection"))!; Assert.Same(entity, same); Assert.Equal(localId, same.Id); Assert.False(runtime.TryMarkWorldSpawnPublished(guid)); Assert.True(runtime.ShouldAdvanceRootRuntime(guid)); Assert.Equal(0x01010002u, record.FullCellId); Assert.True(runtime.TryGetAnimationRuntime(localId, out retainedAnimation)); Assert.Same(animation, retainedAnimation); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); } [Fact] public void RootRuntimePausesWhilePendingOrFrozenAndResumesInVisibleCell() { const uint guid = 0x70000027u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); runtime.MaterializeLiveEntity( guid, spawn.Position!.Value.LandblockId, id => Entity(id, guid)); Assert.True(runtime.ShouldAdvanceRootRuntime(guid)); Assert.True(runtime.RebucketLiveEntity(guid, 0x02020001u)); Assert.False(runtime.ShouldAdvanceRootRuntime(guid)); spatial.AddLandblock(EmptyLandblock(0x0202FFFFu)); Assert.True(runtime.ShouldAdvanceRootRuntime(guid)); Assert.True(runtime.TryApplyState( new SetState.Parsed( guid, (uint)(PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Frozen), 1, 2), out _)); Assert.False(runtime.ShouldAdvanceRootRuntime(guid)); Assert.True(runtime.TryApplyState( new SetState.Parsed( guid, (uint)PhysicsStateFlags.ReportCollisions, 1, 3), out _)); Assert.True(runtime.ShouldAdvanceRootRuntime(guid)); } [Fact] public void PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder() { const uint stateBeforeBindGuid = 0x70000037u; const uint bindBeforeStateGuid = 0x70000038u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); runtime.RegisterLiveEntity(Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u)); PhysicsStateFlags firstState = PhysicsStateFlags.Hidden | PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions; Assert.True(runtime.TryApplyState( new SetState.Parsed(stateBeforeBindGuid, (uint)firstState, 1, 2), out _)); var lateBody = new RemoteMotionRuntime(); runtime.SetRemoteMotionRuntime(stateBeforeBindGuid, lateBody); var earlyBody = new RemoteMotionRuntime(); runtime.SetRemoteMotionRuntime(bindBeforeStateGuid, earlyBody); PhysicsStateFlags secondState = PhysicsStateFlags.Static | PhysicsStateFlags.Ethereal | PhysicsStateFlags.NoDraw; Assert.True(runtime.TryApplyState( new SetState.Parsed(bindBeforeStateGuid, (uint)secondState, 1, 2), out _)); Assert.Equal((firstState & ~PhysicsStateFlags.ReportCollisions) | PhysicsStateFlags.IgnoreCollisions, lateBody.Body.State); Assert.Equal(secondState, earlyBody.Body.State); } [Fact] public void ParentDrivenChildNoDrawKeepsBoundBodyInCanonicalState() { const uint guid = 0x70000039u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); var remote = new RemoteMotionRuntime(); runtime.SetRemoteMotionRuntime(guid, remote); Assert.True(runtime.SetAttachedChildNoDraw(guid, true)); Assert.True(remote.Body.State.HasFlag(PhysicsStateFlags.NoDraw)); Assert.True(runtime.SetAttachedChildNoDraw(guid, false)); Assert.False(remote.Body.State.HasFlag(PhysicsStateFlags.NoDraw)); } [Fact] public void InitiallyVisibleCreate_DoesNotManufactureUnHideTransition() { const uint guid = 0x70000040u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record)); Assert.True(record.TryDequeueStateTransition(out RetailPhysicsStateTransition transition)); Assert.Equal(RetailHiddenTransition.None, transition.HiddenTransition); Assert.Equal(PhysicsStateFlags.ReportCollisions, record.FinalPhysicsState); } [Fact] public void HiddenCreate_SuppressesMeshInteractionButRetainsNarrowObjectTick() { const uint guid = 0x70000041u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); runtime.RegisterLiveEntity(Spawn( guid, 1, 1, 0x01010001u, PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions)); WorldEntity entity = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.False(entity.IsDrawVisible); Assert.Empty(runtime.WorldEntities); Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value); Assert.Single(spatial.Entities); Assert.True(runtime.ShouldAdvanceRootRuntime(guid)); Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record)); Assert.Equal(PhysicsStateFlags.None, record.FinalPhysicsState & PhysicsStateFlags.ReportCollisions); Assert.NotEqual(PhysicsStateFlags.None, record.FinalPhysicsState & PhysicsStateFlags.IgnoreCollisions); } [Fact] public void HiddenThenVisibleState_RestoresSameMeshAndInteractionIdentity() { const uint guid = 0x70000042u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); WorldEntity entity = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.True(runtime.TryApplyState(new SetState.Parsed( guid, (uint)(PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions), 1, 2), out _, out RetailPhysicsStateTransition hidden)); Assert.Equal(RetailHiddenTransition.BecameHidden, hidden.HiddenTransition); Assert.False(entity.IsDrawVisible); Assert.Empty(runtime.WorldEntities); Assert.False(runtime.TryGetInteractionEligibleEntity(guid, out _)); Assert.True(runtime.TryApplyState(new SetState.Parsed( guid, 0u, 1, 3), out _, out RetailPhysicsStateTransition visible)); Assert.Equal(RetailHiddenTransition.BecameVisible, visible.HiddenTransition); Assert.True(entity.IsDrawVisible); Assert.True(runtime.TryGetInteractionEligibleEntity(guid, out var eligible)); Assert.Same(entity, eligible); Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value); Assert.Same(entity, Assert.Single(spatial.Entities)); } [Fact] public void PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp() { const uint guid = 0x70000043u; var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); Assert.True(runtime.TryApplyPickup( new PickupEvent.Parsed(guid, 1, 2), out _)); var update = new WorldSession.EntityPositionUpdate( guid, new CreateObject.ServerPosition( 0x01010002u, 12f, 13f, 5f, 1f, 0f, 0f, 0f), null, null, true, 1, 3, 0, 0); Assert.True(runtime.TryApplyPosition( update, isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out PositionTimestampDisposition disposition, out _, out AcceptedPhysicsTimestamps timestamps)); Assert.Equal(PositionTimestampDisposition.Apply, disposition); Assert.False(timestamps.TeleportAdvanced); Assert.True(timestamps.TeleportHookRequired); } [Fact] public void PositionFromPendingProjection_RequiresTeleportHookWithEqualTeleportStamp() { const uint guid = 0x70000044u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid)); spatial.RemoveLandblock(0x0101FFFFu); Assert.True(runtime.TryApplyPosition( new WorldSession.EntityPositionUpdate( guid, new CreateObject.ServerPosition( 0x01010002u, 12f, 13f, 5f, 1f, 0f, 0f, 0f), null, null, true, 1, 2, 0, 0), isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out PositionTimestampDisposition disposition, out _, out AcceptedPhysicsTimestamps timestamps)); Assert.Equal(PositionTimestampDisposition.Apply, disposition); Assert.False(timestamps.TeleportAdvanced); Assert.True(timestamps.TeleportHookRequired); } [Fact] public void LandblockUnload_HidesTopLevelViewAndReloadRestoresSameIdentity() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000024u, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( spawn.Guid, 0x01010001u, id => Entity(id, spawn.Guid))!; spatial.RemoveLandblock(0x0101FFFFu); Assert.Empty(runtime.WorldEntities); Assert.True(runtime.TryGetRecord(spawn.Guid, out LiveEntityRecord record)); Assert.True(record.IsSpatiallyProjected); Assert.False(record.IsSpatiallyVisible); Assert.Equal(1, spatial.PendingLiveEntityCount); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value); Assert.True(record.IsSpatiallyVisible); Assert.Equal(1, resources.RegisterCount); } [Fact] public void LoadedToPendingToLoaded_RemovesOldDrawSlotWithoutLogicalRecreate() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x70000009u, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); WorldEntity entity = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid))!; Assert.Same(entity, Assert.Single(spatial.Entities)); Assert.True(runtime.RebucketLiveEntity(spawn.Guid, 0x01020001u)); Assert.Empty(spatial.Entities); Assert.Equal(1, spatial.PendingLiveEntityCount); Assert.Equal(1, resources.RegisterCount); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); Assert.Same(entity, Assert.Single(spatial.Entities)); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); } [Fact] public void SessionClear_IsSymmetricAndIdempotent() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); int runtimeTearDowns = 0; var runtime = new LiveEntityRuntime(spatial, resources, _ => runtimeTearDowns++); WorldSession.EntitySpawn spawn = Spawn(0x70000008u, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid)); runtime.Clear(); runtime.Clear(); Assert.Equal(0, runtime.Count); Assert.Empty(runtime.WorldEntities); Assert.Empty(spatial.Entities); Assert.Equal(1, resources.RegisterCount); Assert.Equal(1, resources.UnregisterCount); Assert.Equal(1, runtimeTearDowns); } [Fact] public void ResourceRegistrationFailure_RollsBackMaterializedIdentityAndSpatialState() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new FailingRegisterResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x7000000Au, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); Assert.Throws(() => runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid))); Assert.True(runtime.TryGetRecord(spawn.Guid, out LiveEntityRecord record)); Assert.Null(record.WorldEntity); Assert.Equal(0, runtime.MaterializedCount); Assert.Empty(runtime.WorldEntities); Assert.Empty(spatial.Entities); Assert.Equal(1, resources.RegisterCount); Assert.Equal(1, resources.UnregisterCount); } [Fact] public void SessionClear_AttemptsEveryTeardownAndClearsIdentityWhenOneResourceFails() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new FailingUnregisterResources(0x7000000Bu); var runtime = new LiveEntityRuntime(spatial, resources); foreach (uint guid in new[] { 0x7000000Bu, 0x7000000Cu }) { WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); runtime.MaterializeLiveEntity( guid, spawn.Position!.Value.LandblockId, id => Entity(id, guid)); } Assert.Throws(() => runtime.Clear()); Assert.Equal(2, resources.UnregisterCount); Assert.Equal(0, runtime.Count); Assert.Equal(0, runtime.MaterializedCount); Assert.Empty(runtime.WorldEntities); Assert.Empty(spatial.Entities); } [Fact] public void GenerationReplacement_InstallsNewRecordBeforeReportingOldCleanupFailure() { const uint guid = 0x7000000Eu; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new FailingUnregisterResources(guid); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn first = Spawn(guid, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(first); runtime.MaterializeLiveEntity( guid, first.Position!.Value.LandblockId, id => Entity(id, guid)); LiveEntityRegistrationResult replacement = runtime.RegisterLiveEntity( Spawn(guid, 2, 1, 0x01010001u)); Assert.NotNull(replacement.PriorGenerationCleanupFailure); Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record)); Assert.Equal((ushort)2, record.Generation); Assert.Null(record.WorldEntity); WorldEntity installed = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid))!; Assert.Equal(guid, installed.ServerGuid); Assert.Single(spatial.Entities); } [Fact] public void CanonicalOnlyRebucket_DoesNotOverwriteAuthoritativeFullCell() { const uint guid = 0x7000000Fu; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010022u)); runtime.MaterializeLiveEntity(guid, 0x01010022u, id => Entity(id, guid)); runtime.RebucketLiveEntity(guid, 0x0102FFFFu); Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record)); Assert.Equal(0x01010022u, record.FullCellId); Assert.Equal(0x0102FFFFu, record.CanonicalLandblockId); runtime.RebucketLiveEntity(guid, 0x01020033u); Assert.Equal(0x01020033u, record.FullCellId); } [Fact] public void AttachedProjection_CrossesLandblocksWithoutChangingIdentityOrResources() { const uint guid = 0x70000010u; var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); WorldEntity attached = runtime.MaterializeLiveEntity( guid, 0x01010001u, id => Entity(id, guid), LiveEntityProjectionKind.Attached)!; runtime.RebucketLiveEntity(guid, 0x01020001u); Assert.Same(attached, Assert.Single(spatial.Entities)); Assert.Empty(runtime.WorldEntities); Assert.Equal(1, resources.RegisterCount); Assert.Equal(0, resources.UnregisterCount); var destination = Assert.Single( spatial.LandblockEntries, entry => entry.LandblockId == 0x0102FFFFu); Assert.Same(attached, Assert.Single(destination.Entities)); } [Fact] public void LocalIdAllocator_WrapsInsideLiveNamespaceWithoutReturningZero() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var runtime = new LiveEntityRuntime( spatial, new RecordingResources(), firstLocalEntityId: LiveEntityRuntime.LastLiveEntityId); WorldSession.EntitySpawn first = Spawn(0x70000011u, 1, 1, 0x01010001u); WorldSession.EntitySpawn second = Spawn(0x70000012u, 1, 1, 0x01010001u); runtime.RegisterLiveEntity(first); runtime.RegisterLiveEntity(second); WorldEntity firstEntity = runtime.MaterializeLiveEntity( first.Guid, 0x01010001u, id => Entity(id, first.Guid))!; WorldEntity secondEntity = runtime.MaterializeLiveEntity( second.Guid, 0x01010001u, id => Entity(id, second.Guid))!; Assert.Equal(LiveEntityRuntime.LastLiveEntityId, firstEntity.Id); Assert.Equal(LiveEntityRuntime.FirstLiveEntityId, secondEntity.Id); Assert.NotEqual(0u, secondEntity.Id); Assert.True(runtime.TryGetLocalEntityId(second.Guid, out uint resolved)); Assert.Equal(secondEntity.Id, resolved); Assert.Throws(() => new LiveEntityRuntime( new GpuWorldState(), new RecordingResources(), firstLocalEntityId: uint.MaxValue)); } [Fact] public void Delete_CleansLogicalRecordEvenWhenPreTeardownCallbackFails() { var spatial = new GpuWorldState(); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); var resources = new RecordingResources(); var runtime = new LiveEntityRuntime(spatial, resources); WorldSession.EntitySpawn spawn = Spawn(0x7000000Du, 4, 1, 0x01010001u); runtime.RegisterLiveEntity(spawn); runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position!.Value.LandblockId, id => Entity(id, spawn.Guid)); Assert.Throws(() => runtime.UnregisterLiveEntity( new DeleteObject.Parsed(spawn.Guid, spawn.InstanceSequence), isLocalPlayer: false, beforeTeardown: () => throw new InvalidOperationException("fixture callback failure"))); Assert.Equal(0, runtime.Count); Assert.Empty(runtime.WorldEntities); Assert.Empty(spatial.Entities); Assert.Equal(1, resources.UnregisterCount); } private static LoadedLandblock EmptyLandblock(uint canonicalId) => new(canonicalId, new LandBlock(), Array.Empty()); private static void ResolveParent(LiveEntityRuntime runtime, uint childGuid) => runtime.ParentAttachments.Resolve( childGuid, guid => runtime.TryGetSnapshot(guid, out _), guid => runtime.TryGetSnapshot(guid, out WorldSession.EntitySpawn spawn) ? spawn.InstanceSequence : null, update => runtime.TryApplyParent(update, out _)); private static WorldEntity Entity(uint id, uint guid) => new() { Id = id, ServerGuid = guid, SourceGfxObjOrSetupId = 0x02000001u, Position = System.Numerics.Vector3.Zero, Rotation = System.Numerics.Quaternion.Identity, MeshRefs = Array.Empty(), }; private static WorldSession.EntitySpawn Spawn( uint guid, ushort instance, ushort positionSequence, uint cell, PhysicsStateFlags state = PhysicsStateFlags.ReportCollisions) { var position = new CreateObject.ServerPosition( cell, 10f, 10f, 5f, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps( positionSequence, 1, 1, 1, 0, 1, 0, 1, instance); var physics = new PhysicsSpawnData( RawState: (uint)state, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: 0x09000001u, SoundTableId: null, PhysicsScriptTableId: null, Parent: null, Children: null, Scale: null, Friction: null, Elasticity: null, Translucency: null, Velocity: null, Acceleration: null, AngularVelocity: null, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); return new WorldSession.EntitySpawn( guid, position, 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), null, null, "fixture", null, null, 0x09000001u, PhysicsState: (uint)state, InstanceSequence: instance, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: positionSequence, Physics: physics); } }