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 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!)); 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 _)); } [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); WorldEntity same = runtime.MaterializeLiveEntity( spawn.Guid, spawn.Position.Value.LandblockId, _ => throw new InvalidOperationException("attachment reconstructed"), LiveEntityProjectionKind.World)!; Assert.Same(attached, same); 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 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) { 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)PhysicsStateFlags.ReportCollisions, 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)PhysicsStateFlags.ReportCollisions, InstanceSequence: instance, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: positionSequence, Physics: physics); } }