using System.Net; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.Spells; using AcDream.Runtime.Entities; using AcDream.Runtime.Gameplay; using AcDream.Runtime.Session; using AcDream.Runtime.World; namespace AcDream.Runtime.Tests.Session; public sealed class RuntimeLiveEntitySessionControllerTests { [Fact] public void DirectSinkOwnsCanonicalCreateUpdateDeleteWithoutProjection() { // C3c: the direct sink's Create now enters the canonical initial // residence; this test drives the remote first-entry conductor to // completion (the direct-host pump's job) before the follow-up // Position flows the ordinary post-residence path. using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); // C3c-R1 review R3: the residence route requires a drive-backed // world projection — a content-less (projection-less) controller // keeps the pre-flip legacy registration instead (see // ContentLessDirectSink_KeepsPreFlipLegacyRegistration). var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(0x70000001u, incarnation: 1); sink.Spawned(spawn); drive.DriveAll(); Assert.Equal(0, drive.PendingCount); DrainPlacementFifo(runtime); sink.PositionUpdated(new WorldSession.EntityPositionUpdate( spawn.Guid, spawn.Position!.Value with { PositionX = 20f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 0)); Assert.Equal(1, runtime.Entities.Count); Assert.Equal(1, runtime.Inventory.ObjectCount); Assert.True( runtime.EntityObjects.Entities.TryGetActive( spawn.Guid, out RuntimeEntityRecord canonical)); Assert.Equal( 20f, canonical.Snapshot.Position!.Value.PositionX); sink.Deleted(new DeleteObject.Parsed(spawn.Guid, 1)); Assert.Equal(0, runtime.Entities.Count); Assert.Equal(0, runtime.Inventory.ObjectCount); Assert.Equal( 0, runtime.EntityObjects.Entities.PendingTeardownCount); } /// /// C3c-R1 review R3: a CONTENT-LESS headless host (validated-legal /// configuration: HeadlessConfigurationLoader accepts a null /// process.content) builds no world projection and no first-entry /// drive. Its Creates must keep the exact pre-flip legacy registration /// — the accepted frame commits directly (FullCellId derives from the /// wire cell at registration), no residence lease ever opens, and the /// entity/residence/drive ledgers stay at zero — because a residence /// with no drive to pump it would park every Create (and every packet /// FIFO'd behind its pending residence) forever. /// [Fact] public void ContentLessDirectSink_KeepsPreFlipLegacyRegistration() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(0x70000003u, incarnation: 1); sink.Spawned(spawn); Assert.True( runtime.EntityObjects.Entities.TryGetActive( spawn.Guid, out RuntimeEntityRecord canonical)); Assert.Equal( spawn.Position!.Value.LandblockId, canonical.FullCellId); RuntimeEntityObjectOwnershipSnapshot ownership = runtime.EntityObjects.CaptureOwnership(); Assert.Equal(0, ownership.InitialCreateResidenceLeaseCount); Assert.Equal(0, ownership.FirstEntryDrivePendingCount); Assert.Equal(1, runtime.Entities.Count); Assert.Equal(1, runtime.Inventory.ObjectCount); // Position packets flow the ordinary immediate path — nothing is // FIFO'd behind a pending residence. sink.PositionUpdated(new WorldSession.EntityPositionUpdate( spawn.Guid, spawn.Position!.Value with { PositionX = 20f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 0)); Assert.Equal( 20f, canonical.Snapshot.Position!.Value.PositionX); sink.Deleted(new DeleteObject.Parsed(spawn.Guid, 1)); Assert.Equal(0, runtime.Entities.Count); Assert.Equal( 0, runtime.EntityObjects.Entities.PendingTeardownCount); } [Fact] public void DirectSinkCompletesExactPortalAndSendsLoginComplete() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000001u; runtime.PlayerIdentity.ServerGuid = playerGuid; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var gameActions = new List(); session.GameActionCapture = body => gameActions.Add(body); var controller = new RuntimeLiveEntitySessionController( runtime, session); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(playerGuid, incarnation: 1); sink.Spawned(spawn); Assert.Single(gameActions); Assert.Equal(GameActionLoginComplete.Build(), gameActions[0]); sink.Spawned(spawn); Assert.Single(gameActions); gameActions.Clear(); sink.TeleportStarted(1u); sink.PositionUpdated(new WorldSession.EntityPositionUpdate( playerGuid, spawn.Position!.Value with { LandblockId = 0x01020001u, PositionX = 30f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 1, ForcePositionSequence: 0)); Assert.Single(gameActions); Assert.Equal(GameActionLoginComplete.Build(), gameActions[0]); Assert.True(runtime.TransitOwner.CaptureOwnership().IsSessionIdle); Assert.True(runtime.Portal.Snapshot.Completed); Assert.Equal(0x01020001u, runtime.Portal.Snapshot.DestinationCell); } [Fact] public void DirectSinkContentLessSpawn_InvokesOnLoginCompleteSentHookExactlyOnce() { // SF-4 (Campaign OP OP7 review fix, 2026-08-11): the pre-existing // wiring tests drove the login-complete half only through // HeadlessSessionHost.OptionsSeeder's TEST seam, never through this // controller's own production onLoginCompleteSent call sites — a // future edit that dropped or misdirected the OnSpawned // content-less immediate-admission invoke (:199-201) would have // been invisible to the whole suite. Drives it with a counting // Action, matching the fix's "the seeder's TryRun latch sees the // LoginComplete half via the production path" requirement. using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000004u; runtime.PlayerIdentity.ServerGuid = playerGuid; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); session.GameActionCapture = _ => { }; int hookInvocations = 0; var controller = new RuntimeLiveEntitySessionController( runtime, session, onLoginCompleteSent: () => hookInvocations++); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(playerGuid, incarnation: 1); sink.Spawned(spawn); Assert.Equal(1, hookInvocations); // A repeat Create at the same incarnation must not re-fire the // hook — mirrors the pre-existing gameActions.Single() assertion // for the underlying LoginComplete send itself. sink.Spawned(spawn); Assert.Equal(1, hookInvocations); } [Fact] public void DirectSinkPortalCompletion_InvokesOnLoginCompleteSentHookAfterTeleportEnds() { // SF-4 companion: covers TryAdvancePortalCompletion's own // onLoginCompleteSent invoke (the third production hook site lives // one level up in HeadlessSessionHost.CreateEventRoute's direct // first-entry callback, exercised at the host level). This also // doubles as the SF-3 ordering regression test: the hook body reads // TransitOwner state from INSIDE the callback, so if the invoke // ever regressed back to firing between the LoginComplete send and // transit.EndTeleport(), IsSessionIdle would observe false here. using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000005u; runtime.PlayerIdentity.ServerGuid = playerGuid; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); session.GameActionCapture = _ => { }; int hookInvocations = 0; bool? sessionIdleAtSecondHookInvocation = null; var controller = new RuntimeLiveEntitySessionController( runtime, session, onLoginCompleteSent: () => { hookInvocations++; if (hookInvocations == 2) { sessionIdleAtSecondHookInvocation = runtime.TransitOwner.CaptureOwnership().IsSessionIdle; } }); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(playerGuid, incarnation: 1); sink.Spawned(spawn); Assert.Equal(1, hookInvocations); sink.TeleportStarted(1u); sink.PositionUpdated(new WorldSession.EntityPositionUpdate( playerGuid, spawn.Position!.Value with { LandblockId = 0x01020001u, PositionX = 30f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 1, ForcePositionSequence: 0)); Assert.Equal(2, hookInvocations); Assert.True(runtime.Portal.Snapshot.Completed); Assert.True(sessionIdleAtSecondHookInvocation); } [Fact] public void DirectSinkProjectsAcceptedLocalWorldStateThroughOneHostSeam() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000002u; runtime.PlayerIdentity.ServerGuid = playerGuid; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); session.GameActionCapture = _ => { }; var projection = new FixtureWorldProjection(); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: projection); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(playerGuid, incarnation: 1); sink.Spawned(spawn); sink.TeleportStarted(1u); sink.PositionUpdated(new WorldSession.EntityPositionUpdate( playerGuid, spawn.Position!.Value with { LandblockId = 0x01020001u, PositionX = 30f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 1, ForcePositionSequence: 0)); Assert.Equal(1, projection.SpawnCount); Assert.Equal(1, projection.PositionCount); Assert.Equal(1, projection.TeleportStartCount); Assert.Equal(1, projection.PrepareCount); Assert.True(projection.LastSpawnWasLocal); Assert.True(projection.LastPositionWasLocal); Assert.Equal( PositionTimestampDisposition.Apply, projection.LastPositionDisposition); Assert.Equal(playerGuid, projection.LastRecord?.ServerGuid); Assert.Equal(0x01020001u, projection.LastDestination.CellId); Assert.True(runtime.TransitOwner.CaptureOwnership().IsSessionIdle); Assert.True(runtime.Portal.Snapshot.Completed); } /// /// R2/R3 review fix (2026-08-03). No accepted-position drive is supplied /// (mirrors 's /// "content-less" shape, applied to route 2 specifically), so /// TryExecuteAcceptedLocalPosition can never run and every /// ForcePosition resolves NotApplicable at the call site. Proves /// two things the first implementation pass got wrong: (R2) /// /// still fires — a host is not left holding a stale collision/streaming /// window just because the drive is momentarily absent or NotApplicable /// — and (R3) the pre-existing ProjectPosition fallback still runs /// for a NotApplicable result, exactly like it did before route 2 existed /// (the "no legacy fallback to run instead" comment the review found at /// this exact call site was false). /// [Fact] public void ForcePositionWithoutAnAcceptedPositionDrive_StillCentersAndFallsBackToProjectPosition() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000005u; runtime.PlayerIdentity.ServerGuid = playerGuid; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); session.GameActionCapture = _ => { }; var projection = new FixtureWorldProjection(); // Deliberately no acceptedPositionDrive argument — mirrors a // content-less headless host, where the accepted-position drive is // null and route 2 can never apply. var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: projection); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = Spawn(playerGuid, incarnation: 1); sink.Spawned(spawn); Assert.Equal(1, projection.SpawnCount); Assert.Equal(0, projection.CenterOnForceCount); sink.PositionUpdated(new WorldSession.EntityPositionUpdate( playerGuid, spawn.Position!.Value with { PositionX = 30f, }, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 1)); Assert.Equal(1, projection.CenterOnForceCount); Assert.Equal(playerGuid, projection.LastCenteredRecord?.ServerGuid); // The fallback ran: ProjectPosition observed this exact // ForcePosition disposition, not just the earlier Apply-shaped spawn // follow-up. Assert.Equal(1, projection.PositionCount); Assert.Equal( PositionTimestampDisposition.ForcePosition, projection.LastPositionDisposition); } /// /// C4 route 7 headless gate (D5) — the direct regression test for the /// route-6 scoping's stranded-equipped-child defect: a committed /// child's canonical FullCellId must equal its parent's, driven purely /// through /// with no App/graphical layer involved. Fails without D1/D2/D5. /// [Fact] public void DirectSink_D5_StandaloneParentEventCommitsChildToParentsExactCell() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); CommitLandblockCollision(runtime, 0x01020000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); const uint parentGuid = 0x70000020u; const uint childGuid = 0x70000021u; sink.Spawned(SpawnAt(parentGuid, incarnation: 1, 0x01010001u)); drive.DriveAll(); DrainPlacementFifo(runtime); sink.Spawned(SpawnAt(childGuid, incarnation: 1, 0x01020001u)); drive.DriveAll(); DrainPlacementFifo(runtime); Assert.True(runtime.EntityObjects.Entities.TryGetActive( parentGuid, out RuntimeEntityRecord parent)); Assert.True(runtime.EntityObjects.Entities.TryGetActive( childGuid, out RuntimeEntityRecord child)); Assert.NotEqual(0u, parent.FullCellId); // Distinct starting cells — equality below can only come from D5's // commit, never from coincidence. Assert.NotEqual(parent.FullCellId, child.FullCellId); sink.ParentUpdated(new ParentEvent.Parsed( parentGuid, childGuid, ParentLocation: 0u, PlacementId: 0u, ParentInstanceSequence: 1, ChildPositionSequence: 2)); Assert.Equal(parent.FullCellId, child.FullCellId); Assert.NotEqual(0u, child.FullCellId); } /// /// D5 companion: the deferred flavor — a standalone ParentEvent naming a /// parent whose own CreateObject has not arrived yet must commit once /// that guid becomes addressable (OnSpawned's /// RetryChildrenWaitingForParent). /// [Fact] public void DirectSink_D5_DeferredParentEventCommitsOnceTheParentBecomesAddressable() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); CommitLandblockCollision(runtime, 0x01020000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); const uint parentGuid = 0x70000022u; const uint childGuid = 0x70000023u; sink.Spawned(SpawnAt(childGuid, incarnation: 1, 0x01020001u)); drive.DriveAll(); DrainPlacementFifo(runtime); Assert.True(runtime.EntityObjects.Entities.TryGetActive( childGuid, out RuntimeEntityRecord child)); uint childOriginalCell = child.FullCellId; // The ParentEvent arrives BEFORE the parent's own CreateObject. sink.ParentUpdated(new ParentEvent.Parsed( parentGuid, childGuid, ParentLocation: 0u, PlacementId: 0u, ParentInstanceSequence: 1, ChildPositionSequence: 2)); Assert.Equal(childOriginalCell, child.FullCellId); sink.Spawned(SpawnAt(parentGuid, incarnation: 1, 0x01010001u)); drive.DriveAll(); DrainPlacementFifo(runtime); Assert.True(runtime.EntityObjects.Entities.TryGetActive( parentGuid, out RuntimeEntityRecord parent)); Assert.Equal(parent.FullCellId, child.FullCellId); Assert.NotEqual(0u, child.FullCellId); } /// /// C3c-R1 review F6: the drive controller outlives its session routes, /// so "session reset precedes a new route" is an asserted latch, not a /// silent assumption — a second route cannot attach before the prior /// route detached, and a route that never owned the drive cannot clear /// the live route's tracked entries. /// [Fact] public void FirstEntryDriveServesOneRouteAtATimeAndScopesClearToTheOwner() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; RuntimeFirstEntryDriveController drive = CreateDrive(runtime); var routeA = new object(); var routeB = new object(); drive.AttachRoute(routeA); // Re-attaching the same owner is a no-op; a SECOND route asserts. drive.AttachRoute(routeA); Assert.Throws( () => drive.AttachRoute(routeB)); _ = runtime.EntityObjects.RegisterEntityWithInitialResidence( Spawn(0x70000004u, incarnation: 1), isLocalPlayer: false); Assert.Equal(1, drive.PendingCount); // A non-owner detach (never-attached / displaced route rollback) // must not clear the live route's entries. drive.DetachRoute(routeB); Assert.Equal(1, drive.PendingCount); drive.DetachRoute(routeA); Assert.Equal(0, drive.PendingCount); // After the owner detached, a replacement route may attach. drive.AttachRoute(routeB); drive.DetachRoute(routeB); } [Fact] public void FirstEntryDriveSignalsLocalCompletionOnceAfterCanonicalPlacement() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000003u; runtime.PlayerIdentity.ServerGuid = playerGuid; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); var route = new object(); int completed = 0; drive.AttachRoute(route, record => { Assert.Equal(playerGuid, record.ServerGuid); completed++; }); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); sink.Spawned(Spawn(playerGuid, incarnation: 1)); Assert.Equal(0, completed); DrainFirstEntry(runtime, drive); Assert.Equal(1, completed); Assert.Equal(0, drive.PendingCount); drive.DetachRoute(route); } /// /// D1 (C5b architecture review) — THE discriminating test for the /// no-window host's missing post-merge cell writer. C5b made the /// steady-state merge withhold the wire cell and left the replacement /// writers (AD-60's W2/W3) in AcDream.App, which this route does /// not have and cannot reach. Before the fix a headless remote's /// FullCellId was written at create/placement and then frozen for /// the whole session, no matter how far the server said it walked, and /// RuntimeEntityObjectViews.Snapshot feeds exactly that field to /// every bot's RuntimeEntitySnapshot.CellId. /// [Fact] public void AcceptedRemotePosition_AdvancesCanonicalResidencyInANoWindowHost() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = SpawnAt(0x70000020u, incarnation: 1, 0x01010001u); sink.Spawned(spawn); DrainFirstEntry(runtime, drive); Assert.True(runtime.EntityObjects.Entities.TryGetActive( spawn.Guid, out RuntimeEntityRecord remote)); uint placedCell = remote.FullCellId; Assert.NotEqual(0u, placedCell); // A steady-state Position that crosses into another cell of the SAME // landblock — no teleport channel, no force channel, so this is the // ordinary accepted-Position merge, the exact packet shape C5b // stopped writing residency for. const uint movedCell = 0x01010013u; Assert.NotEqual(movedCell, placedCell); sink.PositionUpdated(PositionUpdate( spawn.Guid, movedCell, positionX: 40f, positionSequence: 2)); Assert.Equal(movedCell, remote.FullCellId); Assert.Equal(0x0101FFFFu, remote.CanonicalLandblockId); // The bot-visible projection, which is the observable this defect // actually broke. Assert.True(runtime.Entities.TryGet( spawn.Guid, out RuntimeEntitySnapshot view)); Assert.Equal(movedCell, view.CellId); } /// /// D1's local-player half. AP-146 enumerates three edges that refresh /// the local player's canonical cell; C5b moved the inbound-Position /// one out of the merge and into an App-only writer, so a no-window /// host lost it entirely. The consequence is not cosmetic: /// RuntimeSetPositionState.IsAffectedCollisionResident reads /// FullCellId to decide which bodies a landblock retirement /// parks, so a bot that runs A->B without teleporting would have /// retired A while parking a body that is physically in B. /// [Fact] public void AcceptedLocalPlayerPosition_AdvancesCanonicalResidencyInANoWindowHost() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; const uint playerGuid = 0x50000020u; runtime.PlayerIdentity.ServerGuid = playerGuid; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); sink.Spawned(SpawnAt(playerGuid, incarnation: 1, 0x01010001u)); DrainFirstEntry(runtime, drive); Assert.True(runtime.EntityObjects.Entities.TryGetActive( playerGuid, out RuntimeEntityRecord player)); uint placedCell = player.FullCellId; Assert.NotEqual(0u, placedCell); const uint movedCell = 0x01010021u; Assert.NotEqual(movedCell, placedCell); sink.PositionUpdated(PositionUpdate( playerGuid, movedCell, positionX: 55f, positionSequence: 2)); Assert.Equal(movedCell, player.FullCellId); Assert.Equal(0x0101FFFFu, player.CanonicalLandblockId); } /// /// D1's negative half, stated as three separate rules rather than one /// aggregate assertion. /// /// /// (a) A Rejected disposition writes NOTHING — the shape the /// graphical route gets for free by returning false from /// LiveEntityInboundAuthorityGate.TryAcceptPosition ahead of /// every wire-cell writer. (b) An active initial-create residence /// suppresses the commit, mirroring /// LiveEntityRuntime.RebucketLiveEntity's own early return: /// while the lease is live, Runtime's SetPosition conductor is the sole /// cell authority and a wire cell must not pre-empt it. (c) A /// LANDBLOCK-shaped id preserves the exact cell instead of coarsening /// it — the rule fact 4 of this fix's brief warned about, asserted /// directly against the shared derivation. /// /// [Fact] public void WireCellCommit_HonoursRejection_Residence_AndTheLandblockPreserveRule() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = SpawnAt(0x70000021u, incarnation: 1, 0x01010001u); // (b) The residence is open between Spawned and the drive's drain. sink.Spawned(spawn); Assert.True(runtime.EntityObjects.Entities.TryGetActive( spawn.Guid, out RuntimeEntityRecord remote)); Assert.True(runtime.EntityObjects.TryGetInitialCreateResidence( remote, out _)); uint duringResidence = remote.FullCellId; sink.PositionUpdated(PositionUpdate( spawn.Guid, 0x01010031u, positionX: 12f, positionSequence: 2)); Assert.Equal(duringResidence, remote.FullCellId); DrainFirstEntry(runtime, drive); uint placedCell = remote.FullCellId; Assert.NotEqual(0u, placedCell); // (a) A stale position sequence is Rejected by the timestamp gate. // The cell is 0x…0011 rather than the 0x…0041 this fixture used // before the C5b follow-up: a low word above 0x40 is not a legal // landcell (LandDefs.CellLowInRange, retail // LandDefs::inbound_valid_cellid @0x004979A0), so once this route // gained the graphical route's payload gate the packet would have // been refused for its CELL and this assertion would have passed // without the timestamp gate ever being consulted. sink.PositionUpdated(PositionUpdate( spawn.Guid, 0x01010011u, positionX: 13f, positionSequence: 1)); Assert.Equal(placedCell, remote.FullCellId); // (c) The landblock-shaped id preserves the exact cell. Assert.True(runtime.EntityObjects.CommitWireCellRebucket( remote, 0x0202FFFFu)); Assert.Equal(placedCell, remote.FullCellId); Assert.Equal(0x0202FFFFu, remote.CanonicalLandblockId); } /// /// C5b follow-up (retail F2 / architecture L-A): the no-window route /// refuses an invalid wire payload with the same rule, at the same /// point, as the graphical route's payloadIsValid gate /// (LiveEntityNetworkUpdateController.OnPosition → /// ProjectileController.CanAcceptPositionPayload → /// LiveEntityInboundAuthorityGate's !payloadIsValid /// return). /// /// /// The three shapes are the three terms of that rule. LandblockId /// 0 is the one that mattered: LandDefs.InboundValidCellId rejects /// a 0 low word, and since D1 an unvalidated 0 flowed straight into /// CommitWireCellRebucket, whose own doc calls cell 0 + landblock /// 0 "the withdrawal shape" — silently de-residencing the entity in the /// field RuntimeEntityObjectViews.Snapshot hands every bot as /// CellId. /// /// /// /// The last assertion is the discriminating one, and it is what makes /// this a BEFORE-THE-MERGE test rather than a before-the-commit test: a /// valid packet at the SAME position sequence the three refused packets /// carried still applies. If the refusal sat anywhere downstream of /// TryApplyPosition, the timestamp gate would have consumed that /// sequence and the follow-up would be Rejected. /// /// [Fact] public void InvalidPositionPayload_IsRefusedBeforeTheMerge_InANoWindowHost() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; CommitLandblockCollision(runtime, 0x01010000u); RuntimeFirstEntryDriveController drive = CreateDrive(runtime); using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); var controller = new RuntimeLiveEntitySessionController( runtime, session, worldProjection: new FixtureWorldProjection()); LiveEntitySessionSink sink = controller.CreateSink(); WorldSession.EntitySpawn spawn = SpawnAt(0x70000051u, incarnation: 1, 0x01010001u); sink.Spawned(spawn); DrainFirstEntry(runtime, drive); Assert.True(runtime.EntityObjects.Entities.TryGetActive( spawn.Guid, out RuntimeEntityRecord remote)); uint placedCell = remote.FullCellId; uint placedLandblock = remote.CanonicalLandblockId; float placedX = remote.Snapshot.Position!.Value.PositionX; Assert.NotEqual(0u, placedCell); const ushort refusedSequence = 7; // (a) LandblockId 0 — the withdrawal shape. sink.PositionUpdated(PositionUpdate( spawn.Guid, 0u, positionX: 20f, positionSequence: refusedSequence)); Assert.Equal(placedCell, remote.FullCellId); Assert.Equal(placedLandblock, remote.CanonicalLandblockId); Assert.Equal(placedX, remote.Snapshot.Position!.Value.PositionX); // (b) A NaN origin component — retail Frame::IsValid @0x00534ED0. sink.PositionUpdated(PositionUpdate( spawn.Guid, 0x01010031u, positionX: float.NaN, positionSequence: refusedSequence)); Assert.Equal(placedCell, remote.FullCellId); Assert.Equal(placedX, remote.Snapshot.Position!.Value.PositionX); // (c) A non-finite velocity — the graphical gate's third term. WorldSession.EntityPositionUpdate infiniteVelocity = PositionUpdate( spawn.Guid, 0x01010031u, positionX: 22f, positionSequence: refusedSequence) with { Velocity = new System.Numerics.Vector3( float.PositiveInfinity, 0f, 0f), }; sink.PositionUpdated(infiniteVelocity); Assert.Equal(placedCell, remote.FullCellId); Assert.Equal(placedX, remote.Snapshot.Position!.Value.PositionX); // The discriminator: POSITION_TS was never consumed. const uint movedCell = 0x01010031u; sink.PositionUpdated(PositionUpdate( spawn.Guid, movedCell, positionX: 23f, positionSequence: refusedSequence)); Assert.Equal(movedCell, remote.FullCellId); Assert.Equal(23f, remote.Snapshot.Position!.Value.PositionX); } /// /// D1's missile gate. The graphical route sends a BOUND projectile's /// accepted Position to the canonical projectile placement owner and /// returns before AD-60's W2, so no wire cell is committed for it; the /// no-window route must not invent one. An UNBOUND Missile-flagged /// record takes the ordinary remote tail in both hosts — the same /// distinction /// pins on the classifier, asserted here on the residency write. /// [Fact] public void BoundProjectilePosition_CommitsNoWireCell_UnboundMissileDoes() { using StartedRuntime started = StartRuntime(); GameRuntime runtime = started.Runtime; using var session = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9000), new FixtureTransport()); // Content-less direct host: legacy registration, no residence lease, // so the packet reaches the wire-cell decision with nothing else in // the way. var controller = new RuntimeLiveEntitySessionController( runtime, session); LiveEntitySessionSink sink = controller.CreateSink(); const uint boundGuid = 0x70000041u; const uint unboundGuid = 0x70000042u; sink.Spawned(SpawnAt(boundGuid, incarnation: 1, 0x01010001u)); sink.Spawned(SpawnAt(unboundGuid, incarnation: 1, 0x01010001u)); Assert.True(runtime.EntityObjects.Entities.TryGetActive( boundGuid, out RuntimeEntityRecord bound)); Assert.True(runtime.EntityObjects.Entities.TryGetActive( unboundGuid, out RuntimeEntityRecord unbound)); foreach (RuntimeEntityRecord missile in new[] { bound, unbound }) { runtime.EntityObjects.Entities.SetFinalPhysicsState( missile, missile.FinalPhysicsState | PhysicsStateFlags.Missile); } var body = new PhysicsBody { Position = new System.Numerics.Vector3(10f, 10f, 5f), Orientation = System.Numerics.Quaternion.Identity, LastUpdateTime = 1d, State = bound.FinalPhysicsState, TransientState = TransientStateFlags.Active, }; body.SnapToCell(0x01010001u, body.Position, body.Position); runtime.EntityObjects.Entities.SetPhysicsBody(bound, body); runtime.EntityObjects.Physics.BindProjectile( bound, body, new ProjectileCollisionSphere( System.Numerics.Vector3.Zero, 0.1f, 1f)); Assert.NotNull(bound.Projectile); Assert.Null(unbound.Projectile); // 0x…0012, not the 0x…0051 this fixture used before the C5b // follow-up: a low word above 0x40 is not a legal landcell // (LandDefs.CellLowInRange), so the payload gate this route now // shares with the graphical host would refuse BOTH packets and the // missile distinction under test would never be reached. const uint movedCell = 0x01010012u; sink.PositionUpdated(PositionUpdate( boundGuid, movedCell, positionX: 30f, positionSequence: 2)); sink.PositionUpdated(PositionUpdate( unboundGuid, movedCell, positionX: 30f, positionSequence: 2)); Assert.Equal(0x01010001u, bound.FullCellId); Assert.Equal(movedCell, unbound.FullCellId); } private static WorldSession.EntityPositionUpdate PositionUpdate( uint guid, uint cellId, float positionX, ushort positionSequence) => new( guid, new CreateObject.ServerPosition( cellId, positionX, 10f, 5f, 1f, 0f, 0f, 0f), Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: positionSequence, TeleportSequence: 0, ForcePositionSequence: 0); /// /// C3c: initial-residence admission requires a live session generation /// (RuntimeInitialCreateResidenceState.CanAcceptCreate), so these direct /// sink tests start one through the same fixture-session shape /// DirectGameRuntimeCommandAdapterTests uses. /// private sealed class StartedRuntime : IDisposable { internal required GameRuntime Runtime { get; init; } internal required LiveSessionHost Live { get; init; } public void Dispose() { _ = Live.Stop(Runtime.Generation); Runtime.Dispose(); } } private static StartedRuntime StartRuntime() { var operations = new FixtureGameplayOperations(); var sessionOperations = new FixtureSessionOperations(); var runtime = new GameRuntime(new GameRuntimeDependencies( operations, operations, operations, operations, SessionOperations: sessionOperations)); operations.Bind(runtime); var resetHost = new FixtureResetHost(); var options = new LiveSessionConnectOptions( true, "127.0.0.1", 9000, "account", "password"); var live = new LiveSessionHost( runtime.Session, new LiveSessionHostBindings( new LiveSessionRoutingFactories( _ => new FixtureEventRoute(), _ => new FixtureCommandRoute()), generation => runtime.ResetGeneration(generation, resetHost), new LiveSessionSelectionBindings( id => runtime.PlayerIdentity.ServerGuid = id, _ => { }, runtime.CommunicationOwner.Chat.SetLocalPlayerGuid, _ => { }, _ => { }, runtime.ActionOwner.Combat.Clear), new LiveSessionEnteredWorldBindings( _ => { }, () => { }, () => { }, _ => { }, () => { }), (_, _, _) => { }, () => { }, _ => { }, _ => { }), options); LiveSessionStartResult startResult = live.Start(options); Assert.Equal(LiveSessionStartStatus.Connected, startResult.Status); Assert.NotEqual(0UL, runtime.Generation.Value); return new StartedRuntime { Runtime = runtime, Live = live }; } private static void CommitLandblockCollision( GameRuntime runtime, uint landblockId) { runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration( landblockId, 1UL); runtime.EntityObjects.Physics.Engine.AddLandblock( landblockId, new TerrainSurface(new byte[81], new float[256]), Array.Empty(), Array.Empty(), worldOffsetX: 0f, worldOffsetY: 0f); runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration( landblockId, 1UL, ready: true); // 670f307c: a remote conductor resolves its landblock-local Create // origin through Runtime's world frame // (RuntimeSetPositionState.PrepareMover:1526-1544) and parks on // RetrySetupUnavailable until that frame exists. Production publishes // it from the accepted local-player Create // (RuntimeEntityObjectLifetime.RegisterEntityCore:558-570), which // always precedes broadcast Creates. Model it with the resident // landblock it belongs to, so these direct-sink tests keep asserting // exact single-entity ledgers. runtime.EntityObjects.Physics.ObserveLocalWorldFrame( landblockId | 0x0001u, teleportAdvanced: false); } private static RuntimeFirstEntryDriveController CreateDrive( GameRuntime runtime) => new( runtime.EntityObjects, runtime.Clock, new UnusedCollisionSource(), () => PlayerMovementConstructionOptions.Fallback, static _ => new RuntimeLocalPlayerPhysicsActivationPreparation( Radius: 0.48f, Height: 1.835f, RuntimeLocalPlayerShadowDisposition.ProvenShapeless)); private static void DrainFirstEntry( GameRuntime runtime, RuntimeFirstEntryDriveController drive) { for (int attempt = 0; attempt < 8 && drive.PendingCount != 0; attempt++) { drive.DriveAll(); DrainPlacementFifo(runtime); } Assert.Equal(0, drive.PendingCount); } /// /// Drains/acknowledges every still-pending placement receipt (the /// ExecutorCompleted correlation is reaped by its acknowledgement) the /// way a host subscription would. /// private static void DrainPlacementFifo(GameRuntime runtime) { while (runtime.EntityObjects.Physics.SetPosition.TryPeekProjection( out AcDream.Runtime.Physics.RuntimePlacementProjectionSnapshot head)) { if (!runtime.EntityObjects.Physics.SetPosition .AcknowledgeProjection(head.Token)) { break; } } } private sealed class UnusedCollisionSource : AcDream.Content.IPreparedCollisionSource { public AcDream.Content.PreparedAssetPresence ProbeCollision( AcDream.Content.Pak.PakAssetType type, uint sourceFileId) => AcDream.Content.PreparedAssetPresence.Available; public AcDream.Content.PreparedCollisionReadResult< AcDream.Core.Physics.FlatSetupCollision> ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) => AcDream.Content.PreparedCollisionReadResult< AcDream.Core.Physics.FlatSetupCollision>.Missing; public AcDream.Content.PreparedCollisionReadResult< AcDream.Core.Physics.FlatGfxObjCollisionAsset> ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult< AcDream.Core.Physics.FlatCellStructureCollisionAsset> ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionReadResult< AcDream.Core.Physics.FlatEnvCellTopology> ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public AcDream.Content.PreparedCollisionSourceStats CollisionStats => default; public void Dispose() { } } private sealed class FixtureSessionOperations : ILiveSessionOperations { public IPEndPoint ResolveEndpoint(string host, int port) => new(IPAddress.Loopback, port); public WorldSession CreateSession(IPEndPoint endpoint) => new(endpoint, new FixtureTransport()); public void Connect(WorldSession session, string user, string password) { } public CharacterList.Parsed GetCharacters(WorldSession session) => new( 0u, [new CharacterList.Character(0x50000001u, "Direct", 0u)], [], 11, "account", true, true); public void EnterWorld(WorldSession session, int activeCharacterIndex) { } public void Tick(WorldSession session) { } public void DisposeSession(WorldSession session) => session.Dispose(); } private sealed class FixtureEventRoute : ILiveSessionEventRouting { public void Attach() { } public void Dispose() { } } private sealed class FixtureCommandRoute : ILiveSessionCommandRouting { public void Activate() { } public void Dispose() { } } private sealed class FixtureResetHost : IRuntimeGenerationResetHost { public void RetireEntityProjection(RuntimeEntityRecord entity) { } public void DrainEntityProjectionBoundary() { } public void CompleteEntityProjectionRetirement() { } } private static WorldSession.EntitySpawn Spawn( uint guid, ushort incarnation) => SpawnAt(guid, incarnation, 0x01010001u); /// /// C4 route 7 D5 gate: parametrized over the wire /// landblock, so a parent and a child can be seeded at DISTINCT cells — /// equality after the D5 commit can then only come from the /// propagation write, never from a coincidental shared default. /// private static WorldSession.EntitySpawn SpawnAt( uint guid, ushort incarnation, uint landblockId) { var position = new CreateObject.ServerPosition( landblockId, 10f, 10f, 5f, 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: incarnation); var physics = new PhysicsSpawnData( RawState: (uint)PhysicsStateFlags.ReportCollisions, Position: position, Movement: null, AnimationFrame: null, SetupTableId: null, 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, null, [], [], [], null, null, "direct entity", null, null, null, PhysicsState: physics.RawState, InstanceSequence: incarnation, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } private sealed class FixtureTransport : IWorldSessionTransport { public void Send(ReadOnlySpan datagram) { } public void Send( IPEndPoint remote, ReadOnlySpan datagram) { } public int Receive( Span destination, TimeSpan timeout, out IPEndPoint? from) { from = null; return -1; } public ValueTask ReceiveAsync( Memory destination, CancellationToken cancellationToken) => ValueTask.FromException( new OperationCanceledException(cancellationToken)); public void Dispose() { } } private sealed class FixtureWorldProjection : IRuntimeDirectWorldProjection { public int SpawnCount { get; private set; } public int PositionCount { get; private set; } public int TeleportStartCount { get; private set; } public int PrepareCount { get; private set; } public int CenterOnForceCount { get; private set; } public RuntimeEntityRecord? LastCenteredRecord { get; private set; } public bool LastSpawnWasLocal { get; private set; } public bool LastPositionWasLocal { get; private set; } public PositionTimestampDisposition LastPositionDisposition { get; private set; } public RuntimeEntityRecord? LastRecord { get; private set; } public RuntimeTeleportDestination LastDestination { get; private set; } public void ProjectSpawn( RuntimeEntityRecord record, bool isLocalPlayer) { SpawnCount++; LastRecord = record; LastSpawnWasLocal = isLocalPlayer; } public void ProjectPosition( RuntimeEntityRecord record, bool isLocalPlayer, PositionTimestampDisposition disposition) { PositionCount++; LastRecord = record; LastPositionWasLocal = isLocalPlayer; LastPositionDisposition = disposition; } public void CenterOnAcceptedForcePosition(RuntimeEntityRecord record) { CenterOnForceCount++; LastCenteredRecord = record; } public void BeginTeleport() => TeleportStartCount++; public RuntimeDestinationReadiness PrepareDestination( long revealGeneration, RuntimeTeleportDestination destination, RuntimeWorldHostProjectionToken portal) { PrepareCount++; LastDestination = destination; bool indoor = (destination.CellId & 0xFFFFu) >= 0x0100u; return new RuntimeDestinationReadiness( revealGeneration, destination.CellId, indoor, IsUnhydratable: false, RequiredRenderRadius: indoor ? 0 : 1, IsRenderNeighborhoodReady: true, AreCompositeTexturesReady: true, IsCollisionReady: true); } } private sealed class FixtureGameplayOperations : IRuntimeCombatAttackOperations, IRuntimeCombatTargetOperations, IRuntimeCombatModeOperations, IRuntimeSpellCastOperations { private GameRuntime? _runtime; public void Bind(GameRuntime runtime) => _runtime = runtime; public bool CanStartAttack() => false; public void PrepareAttackRequest() { } public bool SendAttack(AttackHeight height, float power) => false; public void SendCancelAttack() { } public bool IsDualWield => false; public bool PlayerReadyForAttack => false; public bool AutoRepeatAttack => false; public bool AutoTarget => false; public uint? SelectClosestTarget() => null; public bool IsInWorld => _runtime?.Session.IsInWorld == true; public IReadOnlyList GetOrderedEquipment() => []; public void NotifyExplicitCombatModeRequest() { } public void SendChangeCombatMode(CombatMode mode) { } public uint LocalPlayerId => _runtime?.PlayerIdentity.ServerGuid ?? 0u; public bool CanSend => false; public bool HasRequiredComponents(uint spellId) => false; public bool IsTargetCompatible( uint targetId, SpellMetadata spell, bool showMessage) => false; public void StopCompletely() { } public void SendUntargeted(uint spellId) { } public void SendTargeted(uint targetId, uint spellId) { } public void DisplayMessage(string message) { } public void IncrementBusy() { } } }