using System.Buffers.Binary; using System.Collections.Immutable; using System.Net; using System.Numerics; using System.Reflection; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Headless.Configuration; using AcDream.Headless.Credentials; using AcDream.Headless.Diagnostics; using AcDream.Headless.Hosting; using AcDream.Headless.Platform; using AcDream.Runtime; using AcDream.Runtime.Entities; using AcDream.Runtime.Gameplay; using AcDream.Runtime.Physics; using AcDream.Runtime.Session; using AcDream.Runtime.World; namespace AcDream.Headless.Tests; public sealed class HeadlessSessionHostTests { [Fact] public void SingleSessionStartsReconnectsAndConvergesWithoutPresentation() { var operations = new FixtureSessionOperations(); using var diagnosticsOutput = new StringWriter(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(diagnosticsOutput), operations); RuntimeSessionStartResult first = host.Start(); ulong firstGeneration = host.Runtime.Generation.Value; host.Tick(0.015d); RuntimeSessionStartResult second = host.Reconnect(); Assert.Equal(RuntimeSessionStartStatus.Connected, first.Status); Assert.Equal(RuntimeSessionStartStatus.Connected, second.Status); Assert.Equal(0x50000002u, first.CharacterId); Assert.Equal("Headless", host.ActiveCharacterName); Assert.True(host.Runtime.Session.IsInWorld); Assert.True(host.Runtime.Generation.Value > firstGeneration); Assert.Equal(2, operations.CreatedSessionCount); Assert.Equal(1, operations.DisposedSessionCount); host.Dispose(); Assert.Equal(2, operations.DisposedSessionCount); Assert.True(host.Runtime.CaptureOwnership().IsConverged); Assert.True(credential.IsDisposed); string diagnostics = diagnosticsOutput.ToString(); Assert.Contains("\"state\":\"start-result\"", diagnostics); Assert.DoesNotContain("password", diagnostics); Assert.DoesNotContain("AcDream.App", diagnostics); } [Fact] public async Task ProcessHostRunsUntilCancellationAndReturnsStableExitCode() { var configuration = new HeadlessConfiguration { Version = 1, Sessions = [ Descriptor( HeadlessCredentialProviderKind.StandardInput, "stdin-bot"), ], }; HeadlessPathSet paths = HeadlessPathSet.Resolve( new HeadlessPathOverrides()); using var diagnostics = new StringWriter(); var operations = new FixtureSessionOperations(); using var host = new HeadlessProcessHost( configuration, paths, new System.IO.StringReader( "process-password" + Environment.NewLine), diagnostics, operations); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); HeadlessExitCode result = await host.RunAsync(cancellation.Token); Assert.Equal(HeadlessExitCode.Success, result); Assert.True(host.Session.Runtime.Session.IsInWorld); Assert.DoesNotContain( "process-password", diagnostics.ToString()); } [Fact] public async Task DirectCredentialsOverrideSingleConfiguredSession() { var configuration = new HeadlessConfiguration { Version = 1, Sessions = [Descriptor()], }; HeadlessPathSet paths = HeadlessPathSet.Resolve( new HeadlessPathOverrides()); using var diagnostics = new StringWriter(); var operations = new FixtureSessionOperations(); using var host = new HeadlessProcessHost( configuration, paths, TextReader.Null, diagnostics, operations, directCredentials: new HeadlessDirectCredentials( "direct-account", "direct-secret")); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); HeadlessExitCode result = await host.RunAsync(cancellation.Token); Assert.Equal(HeadlessExitCode.Success, result); Assert.Equal("direct-account", operations.LastUser); Assert.Equal("direct-secret", operations.LastPassword); Assert.DoesNotContain( "direct-secret", diagnostics.ToString()); } [Fact] public void DirectFrameUsesSharedRetailOrderAndMovementCadence() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); HydrateGroundedPlayer(host.Runtime); var sent = new List<(byte[] Body, double Time)>(); var trace = new RuntimeTraceRecorder(); using IDisposable subscription = host.Runtime.Subscribe(trace); operations.Sessions[^1].GameActionCapture = body => sent.Add(( body, host.Runtime.Clock.SimulationTimeSeconds)); RuntimeCommandResult intent = host.Commands.Movement.SetIntent( host.Runtime.Generation, new MovementInput(Forward: true, Run: true)); host.Tick(0.015d); Assert.True(intent.Accepted); Assert.Contains( trace.Entries, static entry => entry.Kind == RuntimeTraceKind.Movement); Assert.Equal( [ MoveToState.MoveToStateAction, AutonomousPosition.AutonomousPositionAction, ], sent.Select(static entry => ActionOpcode(entry.Body)).ToArray()); sent.Clear(); for (int index = 0; index < 70; index++) host.Tick(0.015d); (byte[] Body, double Time)[] positions = sent .Where(static entry => ActionOpcode(entry.Body) == AutonomousPosition.AutonomousPositionAction) .ToArray(); Assert.InRange(positions.Length, 1, 2); Assert.True(positions[^1].Time >= 1d); if (positions.Length == 2) { Assert.True( positions[1].Time - positions[0].Time >= 0.99d); } Assert.DoesNotContain( sent, entry => ActionOpcode(entry.Body) == MoveToState.MoveToStateAction); } [Fact] public void TeardownRetriesOnlyTheUnfinishedSuffix() { var operations = new FixtureSessionOperations(); var writer = new FailOnceTextWriter(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(writer), operations); Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); writer.FailNextWrite = true; Assert.Throws(host.Dispose); Assert.Equal(1, operations.DisposedSessionCount); host.Dispose(); Assert.Equal(1, operations.DisposedSessionCount); Assert.True(host.Runtime.CaptureOwnership().IsConverged); } [Fact] public void WorldProjectionHydratesCanonicalMovementAndTeleportState() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; const uint player = 0x50000002u; runtime.PlayerIdentity.ServerGuid = player; AddFlatLandblock(runtime.EntityObjects.Physics.Engine); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(player)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision); projection.ProjectSpawn(record, isLocalPlayer: true); PlayerMovementController controller = Assert.IsType( runtime.MovementOwner.Controller); controller.SetPosition( new Vector3(48f, 49f, 50f), 0xA9B40001u); projection.ProjectPosition( record, isLocalPlayer: true, PositionTimestampDisposition.Apply); Assert.Same(controller, runtime.MovementOwner.Controller); Assert.Equal(record.LocalEntityId, controller.LocalEntityId); Assert.Equal(new Vector3(48f, 49f, 50f), controller.Position); Assert.Equal( 0xA9B40000u, controller.CellId & 0xFFFF0000u); Assert.True((controller.CellId & 0xFFFFu) < 0x0100u); projection.BeginTeleport(); Assert.Equal(PlayerState.PortalSpace, controller.State); RuntimeDestinationReadiness readiness = projection.PrepareDestination( revealGeneration: 7, new RuntimeTeleportDestination( player, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 1, ForcePositionSequence: 0, new Position( 0xA9B40001u, new Vector3(96f, 97f, 50f), Quaternion.Identity))); Assert.True(readiness.IsCollisionReady); Assert.False(readiness.IsUnhydratable); Assert.Equal(PlayerState.InWorld, controller.State); Assert.Equal(3, collision.CenterCount); Assert.Equal(0xA9B40001u, collision.LastCell); } [Fact] public void WorldProjectionIgnoresNormalEchoButBlipsForcePosition() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; const uint player = 0x50000003u; runtime.PlayerIdentity.ServerGuid = player; AddFlatLandblock(runtime.EntityObjects.Physics.Engine); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(player)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision); projection.ProjectSpawn(record, isLocalPlayer: true); PlayerMovementController controller = Assert.IsType( runtime.MovementOwner.Controller); controller.SetPosition( new Vector3(48f, 49f, 50f), 0xA9B40001u); projection.ProjectPosition( record, isLocalPlayer: true, PositionTimestampDisposition.Apply); Assert.Equal(new Vector3(48f, 49f, 50f), controller.Position); var force = new WorldSession.EntityPositionUpdate( player, record.Snapshot.Position!.Value with { PositionX = 72f, PositionY = 73f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 1); Assert.True(runtime.EntityObjects.TryApplyPosition( force, isLocalPlayer: true, forcePositionRotation: Quaternion.Identity, currentLocalVelocity: controller.BodyVelocity, projectionRequiresTeleportHook: false, acknowledgeProjection: null, out PositionTimestampDisposition disposition, out _, out _)); Assert.Equal( PositionTimestampDisposition.ForcePosition, disposition); projection.ProjectPosition( record, isLocalPlayer: true, disposition); Assert.Equal(new Vector3(72f, 73f, 50f), controller.Position); Assert.Equal(2, collision.CenterCount); } [Fact] public void PlacementReceiptValidationDoesNotRegainMovementOrPhysicsAuthority() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; const uint player = 0x50000004u; runtime.PlayerIdentity.ServerGuid = player; AddFlatLandblock(runtime.EntityObjects.Physics.Engine); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(player)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var directProjection = new HeadlessSessionWorldProjection( runtime, new FixtureCollisionNeighborhood()); directProjection.ProjectSpawn(record, isLocalPlayer: true); PlayerMovementController controller = Assert.IsType< PlayerMovementController>(runtime.MovementOwner.Controller); Vector3 positionBefore = controller.Position; Quaternion orientationBefore = controller.BodyOrientation; RuntimePhysicsOwnershipSnapshot physicsBefore = runtime.EntityObjects.Physics.CaptureOwnership(); RuntimePlacementProjectionSnapshot receipt = Placement( runtime, record, RuntimePlacementProjectionKind.Place, new Vector3(600f, 601f, 602f), Quaternion.CreateFromAxisAngle(Vector3.UnitY, 1.2f)); var receiptSink = new HeadlessRuntimePlacementProjectionSink(runtime); Assert.True(receiptSink.TryApply(in receipt)); Assert.Equal(positionBefore, controller.Position); Assert.Equal(orientationBefore, controller.BodyOrientation); Assert.Equal( physicsBefore, runtime.EntityObjects.Physics.CaptureOwnership()); } [Fact] public void PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(0x50000005u)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var projection = new HeadlessRuntimePlacementProjectionSink(runtime); RuntimePlacementProjectionSnapshot place = Placement( runtime, record, RuntimePlacementProjectionKind.Place, Vector3.One, Quaternion.Identity); RuntimePlacementProjectionSnapshot stale = place with { Token = place.Token with { Entity = place.Token.Entity with { Incarnation = unchecked((ushort)( place.Token.Entity.Incarnation + 1)), }, }, }; RuntimePlacementProjectionSnapshot discard = stale with { Kind = RuntimePlacementProjectionKind.Discard, Token = stale.Token with { SessionLifetimeVersion = ulong.MaxValue, }, }; Assert.False(projection.TryApply(in stale)); Assert.True(projection.TryApply(in discard)); } [Fact] public void ExecutorCompletedReceiptIsAcknowledgeOnlyRegardlessOfRecordValidity() { // F1: mirrors PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly's // stale-token half - proves ExecutorCompleted is acknowledged // unconditionally (never gated by the record-lookup/portal-shape // checks Place/Withdraw depend on), so a genuinely stale/mismatched // token can never wedge the FIFO behind it. var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(0x50000006u)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var sink = new HeadlessRuntimePlacementProjectionSink(runtime); RuntimePlacementProjectionSnapshot completion = Placement( runtime, record, RuntimePlacementProjectionKind.ExecutorCompleted, Vector3.One, Quaternion.Identity); RuntimePlacementProjectionSnapshot stale = completion with { Token = completion.Token with { Entity = completion.Token.Entity with { Incarnation = unchecked((ushort)( completion.Token.Entity.Incarnation + 1)), }, SessionLifetimeVersion = ulong.MaxValue, }, }; Assert.True(sink.TryApply(in completion)); Assert.True(sink.TryApply(in stale)); } [Fact] public void SessionEventRouteOwnsOneObserverAndUnsubscribesBeforeNetworkDetach() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; int subscriberCountDuringDetach = -1; var inner = new FixtureEventRoute( onDispose: () => subscriberCountDuringDetach = runtime.EntityObjects.Events.PlacementSubscriberCount); var placements = new HeadlessRuntimePlacementProjectionSink(runtime); var route = new HeadlessSessionEventRoute( inner, runtime, placements); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); route.Attach(); route.Attach(); Assert.Equal( 1, runtime.EntityObjects.Events.PlacementSubscriberCount); Assert.Equal(1, inner.AttachCount); route.Dispose(); route.Dispose(); Assert.Equal(0, subscriberCountDuringDetach); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); Assert.Equal(1, inner.DisposeCount); } [Fact] public void ReplacementSessionEventRouteGetsOneFreshObserver() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; var placements = new HeadlessRuntimePlacementProjectionSink(runtime); var first = new HeadlessSessionEventRoute( new FixtureEventRoute(), runtime, placements); first.Attach(); Assert.Equal( 1, runtime.EntityObjects.Events.PlacementSubscriberCount); first.Dispose(); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); var replacement = new HeadlessSessionEventRoute( new FixtureEventRoute(), runtime, placements); replacement.Attach(); Assert.Equal( 1, runtime.EntityObjects.Events.PlacementSubscriberCount); replacement.Dispose(); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); } [Fact] public void SessionEventRouteRetryDoesNotRestorePlacementObserver() { var operations = new FixtureSessionOperations(); using var credential = new HeadlessCredentialSecret( "fixture", "password"); using var host = new HeadlessSessionHost( Descriptor(), credential, new HeadlessDiagnosticWriter(TextWriter.Null), operations); GameRuntime runtime = host.Runtime; var inner = new FixtureEventRoute { DisposeFailuresRemaining = 1, }; var route = new HeadlessSessionEventRoute( inner, runtime, new HeadlessRuntimePlacementProjectionSink(runtime)); route.Attach(); Assert.Throws(route.Dispose); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); Assert.Equal(1, inner.DisposeCount); route.Dispose(); Assert.Equal(2, inner.DisposeCount); Assert.Equal( 0, runtime.EntityObjects.Events.PlacementSubscriberCount); } [Fact] public void CollisionTransactionCancelsPostAdmissionFaultWithoutWithdrawingActiveWorld() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint landblockId = 0xA9B4FFFFu; CompleteCollisionGeneration( physics, landblockId, afterAdmission: null, (admission, prepared) => physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 10f))); Assert.Throws(() => CompleteCollisionGeneration( physics, landblockId, _ => throw new FixtureCollisionPublicationException(), (_, _) => throw new InvalidOperationException( "Staging must not run after the injected admission fault."))); Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f)); RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership(); Assert.Equal(1, ownership.LandblockCount); Assert.Equal(0, ownership.CollisionAdmissionCount); } [Fact] public void CollisionTransactionYieldsTheFirstNonterminalRuntimePoll() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint landblockId = 0xA9B4FFFFu; HeadlessCollisionGenerationTransaction transaction = HeadlessCollisionGenerationTransaction.Begin( physics, landblockId, afterAdmission: null, (admission, prepared) => physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 10f))); HeadlessCollisionGenerationAdvance advance; do { advance = transaction.Advance(); Assert.True(advance.Progressed); } while (!advance.YieldToCaller); Assert.False(advance.Completed); Assert.False(advance.WaitingForProjectionAcknowledgement); Assert.False(transaction.CompletionCommitted); do { advance = transaction.Advance(); Assert.True(advance.Progressed); } while (!advance.Completed && !advance.YieldToCaller); if (!advance.Completed) advance = transaction.Advance(); Assert.True(advance.Completed); Assert.True(transaction.EngineMutationCommitted); Assert.True(transaction.CompletionCommitted); Assert.Equal(0, physics.CaptureOwnership().CollisionAdmissionCount); } [Fact] public void CollisionTransactionRetainsPostEngineCancellationUntilPlaceAck() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint landblockId = 0xA9B4FFFFu; CompleteCollisionGeneration( physics, landblockId, afterAdmission: null, (admission, prepared) => physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 10f))); const uint guid = 0x70004201u; const uint cell = 0xA9B40001u; Vector3 position = new(10f, 10f, 0f); RuntimeEntityRecord record = lifetime.RegisterEntity( Spawn(guid)).Canonical!; lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity); lifetime.Entities.SetFullCell(record, cell, landblockId); var body = new PhysicsBody { Position = position, Orientation = Quaternion.Identity, LastUpdateTime = 1d, State = PhysicsStateFlags.Gravity, TransientState = TransientStateFlags.Active, }; body.SnapToCell(cell, position, position); lifetime.Entities.SetPhysicsBody(record, body); record.ObjectClock.Activate(); physics.AcknowledgeSpatialProjection(record, spatial: true); RuntimePlacementProjectionToken seeded = SeedRuntimePlacement( physics, record, cell, position); Assert.True(physics.SetPosition.AcknowledgeProjection(seeded)); HeadlessCollisionGenerationTransaction transaction = HeadlessCollisionGenerationTransaction.Begin( physics, landblockId, afterAdmission: null, (admission, prepared) => physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 20f))); HeadlessCollisionGenerationAdvance advance; do { advance = transaction.Advance(); } while (!advance.WaitingForProjectionAcknowledgement); Assert.False(transaction.EngineMutationCommitted); Assert.True(physics.SetPosition.TryPeekProjection( out RuntimePlacementProjectionSnapshot withdrawal)); Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token)); do { advance = transaction.Advance(); } while (!advance.WaitingForProjectionAcknowledgement); Assert.True(transaction.EngineMutationCommitted); Assert.True(physics.SetPosition.TryPeekProjection( out RuntimePlacementProjectionSnapshot placement)); Assert.False(transaction.TryCancel()); Assert.True(physics.SetPosition.AcknowledgeProjection(placement.Token)); Assert.True(transaction.TryCancel()); RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership(); Assert.Equal(0, ownership.CollisionPrefixMutationCount); Assert.Equal(0, ownership.CollisionAdmissionCount); } [Fact] public void CollisionTransactionCancelsStagingFaultWithoutWithdrawingActiveWorld() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint landblockId = 0xA9B4FFFFu; CompleteCollisionGeneration( physics, landblockId, afterAdmission: null, (admission, prepared) => physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 10f))); Assert.Throws(() => CompleteCollisionGeneration( physics, landblockId, afterAdmission: null, (admission, prepared) => { physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, 25f)); throw new FixtureCollisionPublicationException(); })); Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f)); RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership(); Assert.Equal(1, ownership.LandblockCount); Assert.Equal(0, ownership.CollisionAdmissionCount); } private static HeadlessSessionDescriptor Descriptor( HeadlessCredentialProviderKind provider = HeadlessCredentialProviderKind.Environment, string credentialReference = "BOT_PASSWORD") => new() { Id = "bot", Endpoint = new HeadlessEndpointDescriptor { Host = "127.0.0.1", Port = 9000, }, Account = "account", Character = new HeadlessCharacterSelector { Name = "headless", }, Policy = new HeadlessBotPolicyDescriptor { Id = "idle", }, Credential = new HeadlessCredentialReference { Provider = provider, Reference = credentialReference, }, }; private static void HydrateGroundedPlayer(GameRuntime runtime) { const uint player = 0x50000002u; PhysicsEngine engine = runtime.EntityObjects.Physics.Engine; AddFlatLandblock(engine); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntity(Spawn(player)) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var controller = new PlayerMovementController(engine); controller.SetPosition( new Vector3(96f, 97f, 50f), 0xA9B40001u, new Vector3(96f, 97f, 50f)); runtime.MovementOwner.Controller = controller; } private static void CompleteCollisionGeneration( RuntimePhysicsState physics, uint landblockId, Action? afterAdmission, Action stage) { HeadlessCollisionGenerationTransaction transaction = HeadlessCollisionGenerationTransaction.Begin( physics, landblockId, afterAdmission, stage); while (true) { HeadlessCollisionGenerationAdvance advance = transaction.Advance(); if (advance.Completed) return; Assert.True(advance.Progressed); Assert.False(advance.WaitingForProjectionAcknowledgement); } } private static RuntimePlacementProjectionToken SeedRuntimePlacement( RuntimePhysicsState physics, RuntimeEntityRecord record, uint cell, Vector3 position) { Type coreMarker = typeof(PhysicsEngine); Type requestType = coreMarker.Assembly.GetType( "AcDream.Core.Physics.PhysicsSetPositionRequest", throwOnError: true)!; Type flagsType = coreMarker.Assembly.GetType( "AcDream.Core.Physics.PhysicsSetPositionFlags", throwOnError: true)!; Type placementClassType = coreMarker.Assembly.GetType( "AcDream.Core.Physics.PhysicsPlacementClass", throwOnError: true)!; object request = Activator.CreateInstance( requestType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, args: [ position, Quaternion.Identity, cell, position, ImmutableArray.Empty, 1f, 0.4f, 0.4f, PhysicsStateFlags.None, ObjectInfoState.None, 0u, Enum.ToObject(placementClassType, 0), Enum.ToObject(flagsType, 0x011u), Vector3.Zero, 0f, 0f, 0u, cell, ], culture: null)!; Type runtimeMarker = typeof(RuntimePhysicsState); Type commandType = runtimeMarker.Assembly.GetType( "AcDream.Runtime.Physics.RuntimeSetPositionCommand", throwOnError: true)!; Type kindType = runtimeMarker.Assembly.GetType( "AcDream.Runtime.Physics.RuntimeSetPositionOperationKind", throwOnError: true)!; object command = Activator.CreateInstance( commandType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, args: [ request, Enum.ToObject(kindType, 2), 10d, 0UL, 0f, 0f, default(RuntimePortalPlacementAuthority), ], culture: null)!; MethodInfo apply = physics.SetPosition.GetType().GetMethod( "Apply", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException("Runtime SetPosition.Apply"); object outcome = apply.Invoke( physics.SetPosition, [record, record.PositionAuthorityVersion, command])!; return (RuntimePlacementProjectionToken)(outcome.GetType().GetProperty( "Projection", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?.GetValue(outcome) ?? throw new MissingMemberException("Runtime placement projection")); } private static RuntimeLandblockCollisionAssets CollisionAssets( uint landblockId, float terrainHeight) { var heights = new byte[81]; var table = new float[256]; table[0] = terrainHeight; return new RuntimeLandblockCollisionAssets( landblockId, new TerrainSurface(heights, table), Array.Empty(), Array.Empty(), 0f, 0f, 0u); } private sealed class FixtureCollisionPublicationException : Exception; private static void AddFlatLandblock(PhysicsEngine engine) { var heights = new byte[81]; Array.Fill(heights, (byte)50); var heightTable = new float[256]; for (int index = 0; index < heightTable.Length; index++) heightTable[index] = index; engine.AddLandblock( 0xA9B4FFFFu, new TerrainSurface(heights, heightTable), [], [], worldOffsetX: 0f, worldOffsetY: 0f); } private static WorldSession.EntitySpawn Spawn(uint guid) { var position = new CreateObject.ServerPosition( 0xA9B40001u, 96f, 97f, 50f, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps( Position: 1, Movement: 1, State: 1, Vector: 1, Teleport: 0, ServerControlledMove: 1, ForcePosition: 0, ObjDesc: 1, Instance: 1); var physics = new PhysicsSpawnData( RawState: (uint)PhysicsStateFlags.ReportCollisions, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: null, 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, [], [], [], null, null, "Headless", null, null, null, PhysicsState: physics.RawState, InstanceSequence: 1, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } private static RuntimePlacementProjectionSnapshot Placement( GameRuntime runtime, RuntimeEntityRecord record, RuntimePlacementProjectionKind kind, Vector3 position, Quaternion orientation) { RuntimeEntityKey key = Assert.IsType(record.Key); var token = new RuntimePlacementProjectionToken( Sequence: 1, Revision: 1, Entity: key, PositionAuthorityVersion: record.PositionAuthorityVersion, SpatialAuthorityVersion: record.SpatialAuthorityVersion, PlacementCommitVersion: record.PlacementCommitVersion, SessionLifetimeVersion: runtime.EntityObjects.Entities.SessionLifetimeVersion, ExactCellId: record.FullCellId, CollisionGeneration: 1, Portal: default); return new RuntimePlacementProjectionSnapshot( token, kind, position, orientation, CellLocalPosition: position, InContact: false, OnWalkable: false); } private static uint ActionOpcode(byte[] body) => BinaryPrimitives.ReadUInt32LittleEndian( body.AsSpan(8, sizeof(uint))); private sealed class FixtureSessionOperations : ILiveSessionOperations { public List Sessions { get; } = []; public int CreatedSessionCount { get; private set; } public int DisposedSessionCount { get; private set; } public string? LastUser { get; private set; } public string? LastPassword { get; private set; } public IPEndPoint ResolveEndpoint(string host, int port) => new(IPAddress.Loopback, port); public WorldSession CreateSession(IPEndPoint endpoint) { CreatedSessionCount++; var session = new WorldSession(endpoint); Sessions.Add(session); return session; } public void Connect( WorldSession session, string user, string password) { LastUser = user; LastPassword = password; } public CharacterList.Parsed GetCharacters( WorldSession session) => new( 0u, [ new CharacterList.Character( 0x50000001u, "Other", 0u), new CharacterList.Character( 0x50000002u, "Headless", 0u), ], [], 11, "account", true, true); public void EnterWorld( WorldSession session, int activeCharacterIndex) { } public void Tick(WorldSession session) { } public void DisposeSession(WorldSession session) { DisposedSessionCount++; session.Dispose(); } } private sealed class FailOnceTextWriter : StringWriter { public bool FailNextWrite { get; set; } public override void WriteLine(string? value) { if (FailNextWrite) { FailNextWrite = false; throw new IOException("fixture write failure"); } base.WriteLine(value); } } private sealed class FixtureCollisionNeighborhood : IHeadlessCollisionNeighborhood { public int CenterCount { get; private set; } public uint LastCell { get; private set; } public void CenterOn(uint fullCellId) { CenterCount++; LastCell = fullCellId; } public bool IsReady(uint fullCellId) => fullCellId == LastCell; } private sealed class FixtureEventRoute( Action? onDispose = null) : ILiveSessionEventRouting { public int AttachCount { get; private set; } public int DisposeCount { get; private set; } public int DisposeFailuresRemaining { get; set; } public void Attach() => AttachCount++; public void Dispose() { DisposeCount++; onDispose?.Invoke(); if (DisposeFailuresRemaining > 0) { DisposeFailuresRemaining--; throw new IOException("fixture route detach failure"); } } } }