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; // C3c: conductor-driven flow — a live generation admits the initial // residence, the flat landblock's collision generation commits, and // the world projection's pump drives the local first-entry conductor // to completion (the deleted SynchronizeLocalPlayer hand-copy's // replacement). Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); const uint player = 0x50000002u; runtime.PlayerIdentity.ServerGuid = player; runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( 0xA9B40000u, 1UL); AddFlatLandblock(runtime.EntityObjects.Physics.Engine); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( 0xA9B40000u, 1UL, ready: true); AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry = CreateFirstEntryDrive(runtime); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision, firstEntry); 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; // C3c: conductor-driven flow — a live generation admits the initial // residence, the flat landblock's collision generation commits, and // the world projection's pump drives the local first-entry conductor // to completion (the deleted SynchronizeLocalPlayer hand-copy's // replacement). Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); const uint player = 0x50000003u; runtime.PlayerIdentity.ServerGuid = player; runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( 0xA9B40000u, 1UL); AddFlatLandblock(runtime.EntityObjects.Physics.Engine); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( 0xA9B40000u, 1UL, ready: true); AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry = CreateFirstEntryDrive(runtime); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision, firstEntry); 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); } /// /// C3c-R1 review F7: a remote Create whose landblock lies outside the /// bounded collision neighborhood's service window can never see its /// deferred placement's collision-generation wake — left alone it would /// pin its residence (and the drive's pending entry) for the whole /// session. The host converts it to the celless completion route before /// the pump: the residence completes with FullCell 0, the accepted wire /// frame stays on the canonical snapshot (the exact pre-flip /// accepted-frame behavior for far remotes), and every ledger converges. /// [Fact] public void FarRemoteCreateCompletesCelllessWithoutPinningItsResidence() { 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; Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); const uint player = 0x5000000Bu; runtime.PlayerIdentity.ServerGuid = player; runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( 0xA9B40000u, 1UL); AddFlatLandblock(runtime.EntityObjects.Physics.Engine); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( 0xA9B40000u, 1UL, ready: true); AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry = CreateFirstEntryDrive(runtime); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision, firstEntry); RuntimeEntityRecord playerRecord = runtime.EntityObjects .RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( playerRecord, playerRecord.CreateIntegrationVersion, playerRecord.Snapshot, replaceGeneration: false)); projection.ProjectSpawn(playerRecord, isLocalPlayer: true); const uint farRemote = 0x70000010u; const uint farCell = 0x00010001u; Assert.False(collision.IsWithinServiceWindow(farCell)); RuntimeEntityRecord remote = runtime.EntityObjects .RegisterEntityWithInitialResidence( Spawn(farRemote, farCell), isLocalPlayer: false) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( remote, remote.CreateIntegrationVersion, remote.Snapshot, replaceGeneration: false)); projection.ProjectSpawn(remote, isLocalPlayer: false); // Celless completion: no pinned residence, FullCell stays 0, the // accepted wire frame survives on the canonical snapshot. Assert.False(runtime.EntityObjects.TryGetInitialCreateResidence( remote, out _)); Assert.Equal(0u, remote.FullCellId); Assert.Equal( farCell, remote.Snapshot.Position!.Value.LandblockId); RuntimeEntityObjectOwnershipSnapshot ownership = runtime.EntityObjects.CaptureOwnership(); Assert.Equal(0, ownership.InitialCreateResidenceLeaseCount); Assert.Equal(0, ownership.FirstEntryDrivePendingCount); Assert.Equal(0, firstEntry.PendingCount); // Draining the placement FIFO the way the host subscription would // converges the completion-receipt ledger too. while (runtime.EntityObjects.Physics.SetPosition.TryPeekProjection( out RuntimePlacementProjectionSnapshot head)) { if (!runtime.EntityObjects.Physics.SetPosition .AcknowledgeProjection(head.Token)) { break; } } Assert.Equal( 0, runtime.EntityObjects.CaptureOwnership() .PendingCompletionReceiptCount); } [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; // C3c: conductor-driven flow — a live generation admits the initial // residence, the flat landblock's collision generation commits, and // the world projection's pump drives the local first-entry conductor // to completion (the deleted SynchronizeLocalPlayer hand-copy's // replacement). Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); const uint player = 0x50000004u; runtime.PlayerIdentity.ServerGuid = player; runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( 0xA9B40000u, 1UL); AddFlatLandblock(runtime.EntityObjects.Physics.Engine); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( 0xA9B40000u, 1UL, ready: true); AcDream.Runtime.Session.RuntimeFirstEntryDriveController firstEntry = CreateFirstEntryDrive(runtime); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var directProjection = new HeadlessSessionWorldProjection( runtime, new FixtureCollisionNeighborhood(), firstEntry); 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; [Fact] public void MissingPreparedCollisionYieldsTypedRetryAndCompletesWhenAvailable() { // C3c contract: the prepared-collision read failure is the // conductor's typed AwaitingCollisionSource retry — no // InvalidDataException (or any exception) escapes the host wiring, // the entity stays a tracked, re-drivable first entry, and the same // sequence completes once the source can serve the Setup. 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; Assert.Equal( RuntimeSessionStartStatus.Connected, host.Start().Status); const uint player = 0x50000021u; runtime.PlayerIdentity.ServerGuid = player; runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( 0xA9B40000u, 1UL); AddFlatLandblock(runtime.EntityObjects.Physics.Engine); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( 0xA9B40000u, 1UL, ready: true); var source = new FlakySetupCollisionSource(); var firstEntry = new AcDream.Runtime.Session .RuntimeFirstEntryDriveController( runtime.EntityObjects, runtime.Clock, source, () => PlayerMovementConstructionOptions.From( runtime.CharacterOwner.MovementSkills.Snapshot), static _ => new RuntimeLocalPlayerPhysicsActivationPreparation( Radius: 0.48f, Height: 1.835f, RuntimeLocalPlayerShadowDisposition.ProvenShapeless)); RuntimeEntityRecord record = runtime.EntityObjects .RegisterEntityWithInitialResidence(Spawn(player), isLocalPlayer: true) .Canonical!; Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn( record, record.CreateIntegrationVersion, record.Snapshot, replaceGeneration: false)); var collision = new FixtureCollisionNeighborhood(); var projection = new HeadlessSessionWorldProjection( runtime, collision, firstEntry); projection.ProjectSpawn(record, isLocalPlayer: true); Assert.True(source.SetupReadAttempts >= 1); Assert.Null(runtime.MovementOwner.Controller); Assert.Equal(1, firstEntry.PendingCount); Assert.Equal(0u, record.FullCellId); source.Available = true; // The session tick's retry pump. firstEntry.DriveAll(); Assert.IsType( runtime.MovementOwner.Controller); Assert.Equal(0, firstEntry.PendingCount); Assert.Equal(0xA9B40000u, record.FullCellId & 0xFFFF0000u); Assert.NotEqual(0u, record.FullCellId); } private sealed class FlakySetupCollisionSource : AcDream.Content.IPreparedCollisionSource { internal bool Available { get; set; } internal int SetupReadAttempts { get; private set; } public AcDream.Content.PreparedAssetPresence ProbeCollision( AcDream.Content.Pak.PakAssetType type, uint sourceFileId) => AcDream.Content.PreparedAssetPresence.Available; public AcDream.Content.PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) { SetupReadAttempts++; if (!Available) { return AcDream.Content.PreparedCollisionReadResult< FlatSetupCollision>.Missing; } return AcDream.Content.PreparedCollisionReadResult< FlatSetupCollision>.Loaded(new FlatSetupCollision( System.Collections.Immutable.ImmutableArray< FlatCollisionCylinder>.Empty, [new FlatCollisionSphere(Vector3.Zero, 0.48f)], height: 0f, radius: 0f, stepUpHeight: 0.4f, stepDownHeight: 0.4f)); } public AcDream.Content.PreparedCollisionReadResult< FlatGfxObjCollisionAsset> ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult< FlatCellStructureCollisionAsset> ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionSourceStats CollisionStats => default; public void Dispose() { } } private static AcDream.Runtime.Session.RuntimeFirstEntryDriveController CreateFirstEntryDrive(GameRuntime runtime) => new( runtime.EntityObjects, runtime.Clock, new LoadedSetupCollisionSource(), () => PlayerMovementConstructionOptions.From( runtime.CharacterOwner.MovementSkills.Snapshot), static _ => new RuntimeLocalPlayerPhysicsActivationPreparation( Radius: 0.48f, Height: 1.835f, RuntimeLocalPlayerShadowDisposition.ProvenShapeless)); private sealed class LoadedSetupCollisionSource : AcDream.Content.IPreparedCollisionSource { public AcDream.Content.PreparedAssetPresence ProbeCollision( AcDream.Content.Pak.PakAssetType type, uint sourceFileId) => AcDream.Content.PreparedAssetPresence.Available; public AcDream.Content.PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) => AcDream.Content.PreparedCollisionReadResult .Loaded(new FlatSetupCollision( System.Collections.Immutable.ImmutableArray< FlatCollisionCylinder>.Empty, [new FlatCollisionSphere(Vector3.Zero, 0.48f)], height: 0f, radius: 0f, stepUpHeight: 0.4f, stepDownHeight: 0.4f)); public AcDream.Content.PreparedCollisionReadResult< FlatGfxObjCollisionAsset> ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult< FlatCellStructureCollisionAsset> ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionSourceStats CollisionStats => default; public void Dispose() { } } 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, uint cellId = 0xA9B40001u) { var position = new CreateObject.ServerPosition( cellId, 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; // C3c-R1 review F7: the fixture window mirrors production's 3x3 // membership around the last requested center; no center yet means // "within" (never convert before the first CenterOn). public bool IsWithinServiceWindow(uint fullCellId) { if (LastCell == 0u) return true; int dx = Math.Abs( (int)((fullCellId >> 24) & 0xFFu) - (int)((LastCell >> 24) & 0xFFu)); int dy = Math.Abs( (int)((fullCellId >> 16) & 0xFFu) - (int)((LastCell >> 16) & 0xFFu)); return dx <= 1 && dy <= 1; } } 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"); } } } }