using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Numerics; using AcDream.App.Input; using AcDream.App.Net; using AcDream.App.Physics; using AcDream.App.Rendering; using AcDream.App.Rendering.Vfx; using AcDream.App.Streaming; using AcDream.App.Update; using AcDream.App.World; using AcDream.Content; using AcDream.Content.Pak; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; using AcDream.Runtime; using AcDream.Runtime.Entities; using AcDream.Runtime.Gameplay; using AcDream.Runtime.Physics; using AcDream.Runtime.Session; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Lib.IO; namespace AcDream.App.Tests.Physics; /// /// C4 route 4b-3, test-plan item 9 (the #312 layer, strong form): drives the /// COMPLETE production entry point — a real /// call, not the /// extracted /// seam the far-snap/steady-state integration tests use — for a remote /// teleport, and asserts the render layer directly: the /// pose, ParentCellId, spatial visibility, and collision-shadow /// publication. #312 shipped because a presentation-restore fix's tests /// asserted only InWorld/clock/residency, never this layer — these /// tests exist so the same class of regression cannot ship silently for the /// teleport arm. /// /// /// Every dependency is a REAL production class except the ~15 interface /// seams LiveEntityNetworkUpdateController takes for local-player-only /// or DAT-backed concerns that a remote-teleport call never reaches (verified /// by reading OnPosition: _dats/_animLoader are read only /// from OnMotion; _localPlayerTeleport/_playerHostSource/ /// _acceptedPositionDrive are gated on update.Guid == /// _playerServerGuid, which the remote guid here never satisfies). The /// canonical is shared between /// and , /// exactly like production's SessionPlayerComposition wires them. /// /// /// /// deliberately does NOT satisfy /// IsPlayerGuid ((guid & 0xFF000000u) == 0x50000000u), so /// OnPosition takes the "GROUNDED ROUTING" creature/NPC branch and /// its own render+shadow tail (the block after the /// #184: sync the NPC shadow... comment), not the sibling copy inside /// the IsPlayerGuid branch above it — the two are separate inline /// copies of the same invariant, not a shared helper. Verified live: a /// sabotage of the wrong (player-guid) copy left both tests green, which is /// what caught the branch mismatch during authoring; sabotaging the actual /// NPC-branch copy fails both tests as expected. /// /// public sealed class LiveEntityNetworkRemoteTeleportPresentationTests { private const uint SourceLandblock = 0xB1000000u; private const uint SourceCell = SourceLandblock | 0x0001u; private const uint DestinationLandblock = 0xB2000000u; private const uint DestinationCell = DestinationLandblock | 0x0001u; private static readonly Vector3 DestinationWorldOffset = new(192f, 0f, 0f); private const uint RemoteGuid = 0x70006001u; /// /// In IsPlayerGuid's 0x50xxxxxx range but distinct from /// the fixture's own NoopIdentitySource.ServerGuid /// (0x50000099u) — an OTHER player's remote character, exactly /// the guid shape that takes OnPosition's IsPlayerGuid /// branch (and its own airborne "landing block" early return) while /// still classifying as a remote (update.Guid != _playerServerGuid). /// private const uint OtherPlayerGuid = 0x50006001u; private const float SpawnHeight = 7f; /// /// Retail's foot-sphere convention (PhysicsEngine.Resolve's /// FootSphereCenterLift, #107 2026-06-10): a body's Z sits this /// far above the terrain sample its foot sphere rests on, center-to- /// contact. A canonical placement onto flat terrain therefore resolves /// to terrainHeight + FootSphereCenterLift, not the raw wire Z. /// private const float FootSphereCenterLift = 0.48f; /// /// The strong form of test-plan item 9's commit half: after a remote /// teleport commits, the render pose equals the /// resolved body pose, ParentCellId equals the resolved cell, the /// entity is spatially visible, and the collision shadow was published /// (re-synced) at the resolved position — not merely registered once at /// spawn. Fails against a broken implementation that skips the teleport /// arm's render/shadow tail (invariant 2): reverting that tail to a /// no-op leaves Entity.Position at the pre-teleport spawn pose and /// the shadow registry's position unsynced, both asserted below. /// [Fact] public void TeleportCommit_RenderEntityMatchesResolvedBody_VisibleAndShadowSynced() { using var fixture = new Fixture(); fixture.PublishDestinationCollision(); fixture.ServiceWindow.Allow(DestinationLandblock); Vector3 spawnPose = fixture.Entity.Position; var destination = new Vector3(12f, 14f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( destination, DestinationCell, teleportSequence: 5)); Assert.True(fixture.Lifetime.Entities.TryGetActive( RemoteGuid, out RuntimeEntityRecord canonical)); Assert.NotNull(canonical.PhysicsBody); PhysicsBody body = canonical.PhysicsBody!; Vector3 resolved = destination + DestinationWorldOffset + new Vector3(0f, 0f, FootSphereCenterLift); Assert.Equal(resolved, body.Position); Assert.NotEqual(spawnPose, body.Position); // Invariant 2: the render entity advances from the RESOLVED body. Assert.Equal(body.Position, fixture.Entity.Position); Assert.Equal(body.Orientation, fixture.Entity.Rotation); Assert.Equal(DestinationCell, fixture.Entity.ParentCellId); Assert.Equal(DestinationCell, canonical.FullCellId); Assert.True(fixture.Runtime.TryGetRecord( RemoteGuid, out LiveEntityRecord liveRecord)); Assert.True(liveRecord.IsSpatiallyVisible); // The collision shadow was RE-PUBLISHED at the resolved position — // not merely the stale spawn-time registration. AcDream.Core.Physics.ShadowEntry shadowEntry = Assert.Single( fixture.Shadows.AllEntriesForDebug(), entry => entry.EntityId == fixture.Entity.Id); Assert.Equal(body.Position, shadowEntry.Position); fixture.DrainPlacementFifo(); } /// /// Test-plan item 9's refused half: invariant 1 (the pose still /// advances) restated at the render layer, plus the #312-specific claim /// — the entity REMAINS visible. Fails against a regression that leaves /// the render entity at its stale pose or withdraws it from the spatial /// working set on a refusal. /// /// /// A4 fix round (2026-08-04): every prior assertion here was ALSO /// satisfied by a regression that misclassified this packet as /// UnroutedCatchUp instead of the teleport arm — its /// ApplyInterpolate hard-place lands the body at the exact same /// wire pose (AP-87's bodyToTarget > 4 m branch) and the SAME /// NPC tail then syncs entity/shadow identically, so none of those /// assertions could tell the two arms apart. Arming sticky BEFORE the /// packet and asserting it was cleared afterward closes that gap: /// UnroutedCatchUp never reaches ApplyRemoteContactRouting's /// teleport branch at all, so it never runs the hook, so a stuck NPC /// misclassified onto that arm would stay stuck — only the real teleport /// arm's hook calls UnStick. /// /// [Fact] public void TeleportRefused_RenderPoseTracksStoredDestination_EntityRemainsVisible() { using var fixture = new Fixture(); fixture.PublishDestinationCollision(); // Deliberately NOT ServiceWindow.Allow(DestinationLandblock) — the // canonical placement refuses, so store_position's fallback is what // must move the body (and the render entity behind it). Vector3 spawnPose = fixture.Entity.Position; var destination = new Vector3(12f, 14f, SpawnHeight); EntityPhysicsHost host = fixture.ArmSticky(stickTargetGuid: 0x70009998u); fixture.Controller.OnPosition(fixture.TeleportUpdate( destination, DestinationCell, teleportSequence: 5)); // The discriminator: only the real teleport arm's hook clears this. Assert.Equal(0u, host.PositionManager.GetStickyObjectId()); Assert.True(fixture.Lifetime.Entities.TryGetActive( RemoteGuid, out RuntimeEntityRecord canonical)); PhysicsBody body = canonical.PhysicsBody!; // Unlike the commit half, a REFUSED placement never runs the // ground-contact sweep that applies FootSphereCenterLift — the // store_position fallback stamps the raw wire position verbatim. Vector3 resolved = destination + DestinationWorldOffset; Assert.Equal(resolved, body.Position); Assert.NotEqual(spawnPose, body.Position); Assert.Equal(body.Position, fixture.Entity.Position); Assert.True(fixture.Runtime.TryGetRecord( RemoteGuid, out LiveEntityRecord liveRecord)); Assert.True(liveRecord.IsSpatiallyVisible); // The collision shadow was RE-PUBLISHED at the stored destination — // the generic top-of-OnPosition wire-pose write never touches the // shadow registry, so this specifically exercises the tail. AcDream.Core.Physics.ShadowEntry shadowEntry = Assert.Single( fixture.Shadows.AllEntriesForDebug(), entry => entry.EntityId == fixture.Entity.Id); Assert.Equal(body.Position, shadowEntry.Position); fixture.DrainPlacementFifo(); } /// /// Test-plan item 6 / contract D5: TS-44's sticky suppression is an /// NPC-only CALLER gate that must NOT suppress the teleport arm — retail /// sticky cannot survive a teleport (UnStick is /// teleport_hook's second action, @0x00514EEE). The exact gate /// under test is /// LiveEntityNetworkUpdateController.OnPosition's /// if (!snapSuppressedByStick || isTeleportRoute) — a stuck NPC's /// teleport packet must still run the hook (observable here as /// UnStick actually clearing the sticky lease) and place /// canonically, exactly as an unstuck NPC's would. /// [Fact] public void StuckNpc_TeleportPacket_RunsTheHookAndPlacesDespiteStickySuppression() { using var fixture = new Fixture(); fixture.PublishDestinationCollision(); fixture.ServiceWindow.Allow(DestinationLandblock); EntityPhysicsHost host = fixture.ArmSticky(stickTargetGuid: 0x70009999u); var destination = new Vector3(12f, 14f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( destination, DestinationCell, teleportSequence: 5)); // The hook's UnStick action actually ran — proof the sticky lease // did not block dispatch, not merely that the placement happened to // succeed for some unrelated reason. Assert.Equal(0u, host.PositionManager.GetStickyObjectId()); Assert.True(fixture.Lifetime.Entities.TryGetActive( RemoteGuid, out RuntimeEntityRecord canonical)); Assert.NotNull(canonical.PhysicsBody); PhysicsBody body = canonical.PhysicsBody!; Vector3 resolved = destination + DestinationWorldOffset + new Vector3(0f, 0f, FootSphereCenterLift); Assert.Equal(resolved, body.Position); Assert.Equal(DestinationCell, canonical.FullCellId); fixture.DrainPlacementFifo(); } /// /// Test-plan item 6 / contract D5, the OnPosition-level restatement: /// "the player arm's landing block (!rmState.Body.InContact /// hard-snap + return) must not claim a teleport-classified packet" and /// "the 4a IsAirborneNoOperation early returns... cannot claim a /// SetPosition route". takes /// OnPosition's IsPlayerGuid branch (a DIFFERENT code path /// than every other test in this file, which use the creature-guid /// branch) — an ungrounded wire packet (IsGrounded: false) for a /// teleport-classified body must still reach the canonical placement /// through that branch's own copy of ApplyRemoteContactRouting, /// not the branch's airborne early return at the top of the /// IsPlayerGuid block. /// [Fact] public void AirborneOtherPlayer_TeleportPacket_PlacesThroughThePlayerArmWithoutTheLandingBlock() { using var fixture = new Fixture(OtherPlayerGuid); fixture.PublishDestinationCollision(); fixture.ServiceWindow.Allow(DestinationLandblock); // Same mid-arc state the routing-seam test above uses: no wire // contact, no body contact — the strongest form of "airborne". fixture.Remote.Airborne = true; fixture.Remote.Body.TransientState = TransientStateFlags.Active; var destination = new Vector3(12f, 14f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( destination, DestinationCell, teleportSequence: 5, guid: OtherPlayerGuid, isGrounded: false)); Assert.True(fixture.Lifetime.Entities.TryGetActive( OtherPlayerGuid, out RuntimeEntityRecord canonical)); Assert.NotNull(canonical.PhysicsBody); PhysicsBody body = canonical.PhysicsBody!; Vector3 resolved = destination + DestinationWorldOffset + new Vector3(0f, 0f, FootSphereCenterLift); Assert.Equal(resolved, body.Position); Assert.Equal(DestinationCell, canonical.FullCellId); // The airborne early return writes NOTHING and returns before the // spatial rebucket that follows a real dispatch — resident // visibility is proof OnPosition did not take that exit. Assert.True(fixture.Runtime.TryGetRecord( OtherPlayerGuid, out LiveEntityRecord liveRecord)); Assert.True(liveRecord.IsSpatiallyVisible); fixture.DrainPlacementFifo(); } /// /// A1 fix round (2026-08-04): the NPC arm must still arm the leash for /// an ordinary landing packet — wire IS grounded (retail's /// arg4 != 0), body is NOT in contact (acdream's own /// snap-vs-interpolate axis, unrelated to retail's arming predicate). /// 's /// free-flight carve-out claims this as AirborneSnap regardless of /// classification (near/far/leftover would all otherwise apply); before /// the fix, ToConstraintArm mapped AirborneSnap to the one /// Runtime value that never arms, so this exact packet — a creature /// knocked off a ledge — armed ZERO times where retail's /// MoveOrTeleport returns nonzero and arms unconditionally /// @0x00454272. host.PositionManager.Constraint is lazily created /// only on a genuine arm, so its presence after the packet is direct, /// unambiguous proof of the fix — not an inference from body position. /// [Fact] public void NpcAirborneSnap_LandingPacket_StillArmsTheLeash() { using var fixture = new Fixture(); EntityPhysicsHost host = fixture.InstallHost(); Assert.Null(host.PositionManager.Constraint); // Mid-arc: body not in contact, wire IS grounded (this packet is an // ordinary landing correction, not a free-flight UP). fixture.Remote.Airborne = true; fixture.Remote.Body.TransientState = TransientStateFlags.Active; var landingPos = new Vector3(12f, 14f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( landingPos, SourceCell, teleportSequence: 1, isGrounded: true)); Assert.NotNull(host.PositionManager.Constraint); } /// /// R3/A2 fix round (2026-08-04): a teleported NPC must not synthesize a /// locomotion velocity from the teleport distance. Before the fix, the /// deleted shared remotePlacementRequired block used to sit ABOVE /// the NPC synth-velocity code and always returned, so a teleport packet /// never reached it; once that block was deleted and the teleport arm /// routed through the NPC tail, the synth-velocity install ran /// unconditionally, computing (worldPos - LastServerPos) / elapsed /// across the WHOLE teleport distance over one packet interval — /// hundreds to thousands of m/s — and installing it as /// ServerVelocity. 's /// LastServerPos/LastServerPosTime are seeded to simulate /// an already-tracked creature (the real @teleto-on-a-visible- /// drudge scenario) so a broken implementation's synth would have a real /// distance/interval to compute from, not a degenerate first-packet /// no-op. /// [Fact] public void NpcTeleport_DoesNotInstallASynthesizedVelocity() { using var fixture = new Fixture(); fixture.PublishDestinationCollision(); fixture.ServiceWindow.Allow(DestinationLandblock); fixture.Remote.LastServerPos = fixture.Entity.Position; fixture.Remote.LastServerPosTime = (DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds - 0.15; var destination = new Vector3(12f, 14f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( destination, DestinationCell, teleportSequence: 5)); Assert.False(fixture.Remote.HasServerVelocity); Assert.Equal(Vector3.Zero, fixture.Remote.ServerVelocity); fixture.DrainPlacementFifo(); } /// /// Test-plan item 10 (R1/A7 fix round, 2026-08-04): "a wire-airborne /// null-classified packet writes exactly AP-135's two fields and nothing /// else (body, entity, queue, leash all untouched)" — driven for the NPC /// arm specifically, which shipped WITHOUT this D2 shape (the player arm /// had it from the start). Before the fix, a wire-airborne, null/ /// Rejected*-classified NPC packet fell through to /// 's /// own free-flight carve-out and received a hard-snap body write, a /// leash arm, and a shadow republish — none of which retail's /// return 0 (@0x0051636D) produces. /// forces the null-classification shape (no local player exists yet to /// supply player_distance — the login-window case D2 is scoped /// to); teleportSequence: 1 matches the spawn's own committed /// teleport timestamp so this packet does NOT advance TELEPORT_TS /// (never teleport-classified, even setting classification aside). /// [Fact] public void NullClassifiedNpc_WireAirbornePacket_WritesOnlyBookkeepingNoBodyOrShadow() { using var fixture = new Fixture(nullClassification: true); EntityPhysicsHost host = fixture.InstallHost(); Assert.Null(host.PositionManager.Constraint); Vector3 spawnBodyPose = fixture.Remote.Body.Position; var wirePos = new Vector3(50f, 50f, SpawnHeight); fixture.Controller.OnPosition(fixture.TeleportUpdate( wirePos, SourceCell, teleportSequence: 1, isGrounded: false)); // D2's core claim: no body write. A broken implementation that lets // this packet reach ApplyRemoteContactRouting's free-flight carve-out // hard-snaps the body to wirePos here. Assert.Equal(spawnBodyPose, fixture.Remote.Body.Position); // No shadow republish — still exactly the one spawn-time entry, at // the spawn pose, not wirePos. AcDream.Core.Physics.ShadowEntry shadowEntry = Assert.Single( fixture.Shadows.AllEntriesForDebug(), entry => entry.EntityId == fixture.Entity.Id); Assert.Equal(spawnBodyPose, shadowEntry.Position); // Round-2 architecture review B1: everything above asserts only what // must NOT happen, so an EMPTIED ApplyWireAirborneLeftoverBookkeeping // passed every test in the tree. AP-135's two writes are the POSITIVE // half of D2's shape and are load-bearing: RemoteMotion.CellId feeds // RuntimeRemotePhysicsUpdater's free-fall sweep gate (its `rm.CellId // != 0` test) and the LastServerPos/LastServerPosTime pair seeds the // first-grounded-packet velocity synthesis. Losing them silently is // the "airborne remote falls through the floor" shape. Assert.Equal(SourceCell, fixture.Remote.CellId); Assert.Equal(wirePos, fixture.Remote.LastServerPos); Assert.NotEqual(0d, fixture.Remote.LastServerPosTime); // Round-2 retail review: this test's name and doc both claim the leash // is untouched — a D4 "no arm" row, because retail returns 0 // @0x0051636D and never reaches ConstrainTo @0x00454272 — but nothing // asserted it. The constraint sub-manager is lazily created only // inside a genuine arm, so null is direct proof of zero arming calls. Assert.Null(host.PositionManager.Constraint); } private sealed class Fixture : IDisposable { internal RuntimeEntityObjectLifetime Lifetime { get; } internal LiveEntityRuntime Runtime { get; } internal LiveEntityNetworkUpdateController Controller { get; } internal RemoteServiceWindow ServiceWindow { get; } = new(); internal ShadowObjectRegistry Shadows { get; } internal WorldEntity Entity { get; } internal RemoteMotion Remote { get; private set; } = null!; private readonly GpuWorldState _spatial; private readonly uint _guid; private readonly bool _nullClassification; /// /// Defaults to (an ordinary creature-style /// guid, NOT in IsPlayerGuid's 0x50xxxxxx range). /// Tests that need the OTHER production remote branch — the /// IsPlayerGuid(update.Guid) block in OnPosition, which /// has its own airborne early return (the "player arm's landing /// block") — pass a guid in that range instead. /// internal Fixture(uint guid = RemoteGuid, bool nullClassification = false) { _guid = guid; _nullClassification = nullClassification; var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() }; engine.AddLandblock( SourceLandblock, new TerrainSurface(new byte[81], new float[256]), Array.Empty(), Array.Empty(), worldOffsetX: 0f, worldOffsetY: 0f); Lifetime = new RuntimeEntityObjectLifetime(engine); Lifetime.BindEventContext( static () => new RuntimeGenerationToken(1UL), static () => 1UL); Shadows = engine.ShadowObjects; var spatial = new GpuWorldState(); // GpuWorldState's own bucket key is the CANONICAL landblock form // ((id & 0xFFFF0000) | 0xFFFF) — the same form // RebucketLiveEntity/RuntimeEntityObjectLifetime derive from a // cell id. Registering the raw 0x....0000 id here would silently // leave the entity in the PENDING bucket forever. spatial.AddLandblock(new LoadedLandblock( CanonicalLandblock(SourceLandblock), new DatReaderWriter.DBObjs.LandBlock(), Array.Empty())); _spatial = spatial; Runtime = new LiveEntityRuntime( spatial, new NoopResources(), NullLiveEntityRuntimeComponentLifecycle.Instance, Lifetime); var wirePosition = new CreateObject.ServerPosition( SourceCell, 10f, 10f, SpawnHeight, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps( Position: 1, Movement: 1, State: 1, Vector: 1, Teleport: 1, ServerControlledMove: 1, ForcePosition: 1, ObjDesc: 1, Instance: 1); var physics = new PhysicsSpawnData( RawState: (uint)PhysicsStateFlags.ReportCollisions, Position: wirePosition, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: 0x09000001u, SoundTableId: null, PhysicsScriptTableId: null, Parent: null, Children: null, Scale: 1f, Friction: null, Elasticity: null, Translucency: null, Velocity: null, Acceleration: null, AngularVelocity: null, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); var spawn = new WorldSession.EntitySpawn( _guid, wirePosition, 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), null, null, "remote-teleport-fixture", null, null, 0x09000001u, PhysicsState: (uint)PhysicsStateFlags.ReportCollisions, InstanceSequence: 1, PositionSequence: 1, MovementSequence: 1, ServerControlSequence: 1, Physics: physics); LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(spawn); Entity = record.WorldEntity ?? throw new InvalidOperationException( "fixture failed to materialize the remote entity"); Assert.True(Runtime.RebucketLiveEntity(_guid, SourceCell)); // Adopts this body as the canonical PhysicsBody (none exists yet) // — mirrors LiveEntityLifecycleStressTests' RecallPortalFixture. var remote = new RemoteMotion(); remote.Body.SnapToCell(SourceCell, Entity.Position, Entity.Position); remote.CellId = SourceCell; Runtime.SetRemoteMotionRuntime(_guid, remote); Remote = remote; Shadows.Register( Entity.Id, 0x02000001u, Entity.Position, Entity.Rotation, radius: 0.48f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: SourceLandblock, collisionType: ShadowCollisionType.Cylinder, cylHeight: 1.835f, seedCellId: SourceCell, isStatic: false); var origin = new LiveWorldOriginState(); origin.SetPlaceholder( (int)((SourceLandblock >> 24) & 0xFFu), (int)((SourceLandblock >> 16) & 0xFFu)); var animatedEntities = new LiveEntityAnimationRuntimeView( new LiveEntityRuntimeSlot()); var remotePlacementDrive = new RuntimeRemotePlacementDriveController( Lifetime, new GameRuntimeClock(), new NoopCollisionSource(), ServiceWindow); var acceptedPositionDrive = new RuntimeAcceptedPositionDriveController( Lifetime, new GameRuntimeClock(), new NoopCollisionSource(), new LocalPlayerOutboundController(static (_, _, _, _, _, _) => { }), static () => new RuntimeGenerationToken(1UL), static () => 0x50000099u, static () => null, static () => false, static () => null); var identity = new NoopIdentitySource(); var deletion = new LiveEntityDeletionController( Runtime, Lifetime, new NoopTeardownCoordinator(), identity); var hydration = new LiveEntityHydrationController( Runtime, Lifetime, new object(), new NoopMaterializer(), new NoopRelationships(), new NoopReadyPublisher(), new AlwaysKnownOrigin(), new NoopNetworkSink(), new NoopTimestampPublisher(), identity, deletion); var entityEffects = new EntityEffectController( Runtime, new AcDream.Core.Vfx.PhysicsScriptRunner( static _ => null, new AcDream.Core.Physics.AnimationHookRouter(), randomUnit: static () => 0.5), new AcDream.Core.Vfx.PhysicsScriptTableResolver(static _ => null), new EntityEffectPoseRegistry()); Controller = new LiveEntityNetworkUpdateController( Runtime, Lifetime.Objects, hydration, entityEffects, new LiveEntityPresentationController( Runtime, Shadows, (_, _, _) => true, new LiveEntityPartArrayEnterWorldPort(_ => { })), new LiveEntityLightController( Runtime, new EntityEffectPoseRegistry(), new AcDream.Core.Lighting.LightingHookSink( new AcDream.Core.Lighting.LightManager(), new EntityEffectPoseRegistry()), static _ => null), new EquippedChildRenderController( new NoopDatReaderWriter(), new object(), Lifetime.Objects, Runtime, new EntityEffectPoseRegistry(), static _ => false, static (_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Superseded, null)), new ProjectileController(Runtime), animatedEntities, new RemoteMovementObservationTracker(), new RemotePhysicsUpdater( Lifetime.Physics, static (_, _) => (0.48f, 1.835f), static (_, _) => ( System.Collections.Immutable .ImmutableArray.Empty, 1f, 0.4f, 0.4f), static (_, _, _, _) => { }), new RemoteInboundMotionDispatcher( static (_, _, _) => false, static (_, _) => { }), new LiveEntityMotionRuntimeController( Runtime, new PhysicsDataCache(), static () => null, new AcDream.Core.Selection.SelectionState(), origin), engine, new NoopDatReaderWriter(), new NoopAnimationLoader(), combatTargetController: null, origin, new NoopTeleportSink(), _nullClassification ? new NoopLocalPlayerControllerSource() : new StubLocalPlayerControllerSource(), new LocalPlayerOutboundController(static (_, _, _, _, _, _) => { }), new NoopPhysicsHostSource(), identity, new FixedScriptTime(), new NoopSessionSource(), publishTimestamps: static (_, _) => { }, new NoopMovementTruthSink(), acceptedPositionDrive, remotePlacementDrive, worldDropProjection: null); } internal void PublishDestinationCollision() { var heights = new byte[81]; Array.Fill(heights, (byte)SpawnHeight); var heightTable = new float[256]; for (int index = 0; index < heightTable.Length; index++) heightTable[index] = index; Lifetime.Physics.ObserveLocalWorldFrame( SourceCell, teleportAdvanced: false); Lifetime.Physics.SetPosition.BeginCollisionGeneration( DestinationLandblock, 1UL); Lifetime.Physics.Engine.AddLandblock( DestinationLandblock, new TerrainSurface(heights, heightTable), Array.Empty(), Array.Empty(), worldOffsetX: DestinationWorldOffset.X, worldOffsetY: DestinationWorldOffset.Y); Lifetime.Physics.SetPosition.CommitCollisionGeneration( DestinationLandblock, 1UL, ready: true); // The destination landblock must also be a LOADED spatial bucket // — not merely collision-ready — or IsLiveEntityProjectionResident // would (correctly) report the entity as not-yet-streamed-in and // mask the render-layer assertions these tests exist to make. // Streaming residency itself is out of scope here; C4 route 4b-3 // is about the physics/render sync tail once a destination is // already resident, matching the two-tier streaming contract. uint destinationCanonical = CanonicalLandblock(DestinationLandblock); if (!_spatial.IsLoaded(destinationCanonical)) { _spatial.AddLandblock(new LoadedLandblock( destinationCanonical, new DatReaderWriter.DBObjs.LandBlock(), Array.Empty())); } } private static uint CanonicalLandblock(uint landblockId) => (landblockId & 0xFFFF0000u) | 0xFFFFu; internal WorldSession.EntityPositionUpdate TeleportUpdate( Vector3 destination, uint cellId, ushort teleportSequence, uint guid = RemoteGuid, bool isGrounded = true) => new( guid, new CreateObject.ServerPosition( cellId, destination.X, destination.Y, destination.Z, 1f, 0f, 0f, 0f), Velocity: null, PlacementId: null, IsGrounded: isGrounded, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: teleportSequence, ForcePositionSequence: 0); /// /// Item 1(b): installs a real on the /// remote's canonical incarnation and arms its sticky lease, exactly /// as the production hydration pipeline does before a live NPC can /// stick to a target. RunRemoteTeleportHook resolves the host /// through _liveEntities.TryGetPhysicsHost, so a test that /// wants the hook's UnStick action to be observable needs the /// SAME installed host, not merely a bound /// reader. /// internal EntityPhysicsHost ArmSticky(uint stickTargetGuid) { EntityPhysicsHost host = InstallHost(); host.PositionManager.StickTo(stickTargetGuid, radius: 1f, height: 1f); Assert.NotEqual(0u, host.PositionManager.GetStickyObjectId()); return host; } /// /// A1 fix round (2026-08-04): installs a real /// WITHOUT arming sticky, so a test /// can observe whether TryArmConstraintAfterOperation actually /// armed the leash via host.PositionManager.Constraint — the /// constraint sub-manager is lazily created only on a real arm, so /// after a packet is direct proof of zero /// arming calls. /// internal EntityPhysicsHost InstallHost() { Assert.True(Runtime.TryGetRecord( _guid, out LiveEntityRecord liveRecord)); var host = new EntityPhysicsHost( _guid, getPosition: () => new AcDream.Core.Physics.Position( Remote.CellId, Remote.Body.Position, Remote.Body.Orientation), getVelocity: () => Remote.Body.Velocity, getRadius: () => 0.48f, inContact: () => Remote.Body.InContact, minterpMaxSpeed: () => null, curTime: () => 0d, physicsTimerTime: () => 0d, getObjectA: _ => null, handleUpdateTarget: _ => { }, interruptCurrentMovement: () => { }); Runtime.InstallPhysicsHost(liveRecord, host); Remote.MarkFullPhysicsHostBound(); return host; } internal void DrainPlacementFifo() { while (Lifetime.Physics.SetPosition.TryPeekProjection( out RuntimePlacementProjectionSnapshot head)) { if (!Lifetime.Physics.SetPosition.AcknowledgeProjection(head.Token)) break; } } public void Dispose() => Lifetime.Dispose(); internal sealed class RemoteServiceWindow : IRuntimeRemotePlacementServiceWindow { private readonly HashSet _within = []; internal void Allow(uint landblockId) => _within.Add((landblockId & 0xFFFF0000u) | 0xFFFFu); public bool IsWithinServiceWindow(uint landblockId) => _within.Contains((landblockId & 0xFFFF0000u) | 0xFFFFu); } private sealed class NoopResources : ILiveEntityResourceLifecycle { public void Register(WorldEntity entity) { } public void Unregister(WorldEntity entity) { } } private sealed class NoopCollisionSource : IPreparedCollisionSource { public PreparedAssetPresence ProbeCollision( PakAssetType type, uint sourceFileId) => PreparedAssetPresence.Available; public PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) => PreparedCollisionReadResult.Loaded( new FlatSetupCollision( System.Collections.Immutable .ImmutableArray.Empty, [new FlatCollisionSphere(Vector3.Zero, 0.48f)], height: 0f, radius: 0f, stepUpHeight: 0.4f, stepDownHeight: 0.4f)); public PreparedCollisionReadResult ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public PreparedCollisionReadResult ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public PreparedCollisionSourceStats CollisionStats => default; public void Dispose() { } } private sealed class NoopIdentitySource : ILocalPlayerIdentitySource { public uint ServerGuid => 0x50000099u; } private sealed class NoopTeardownCoordinator : ILiveEntityTeardownCoordinator { public void TearDown(LiveEntityRecord record) { } public void ForgetUnknownOwner(uint serverGuid) { } } private sealed class NoopMaterializer : ILiveEntityProjectionMaterializer { public bool TryMaterialize( RuntimeEntityRecord expectedCanonical, WorldSession.EntitySpawn canonicalSpawn, LiveProjectionPurpose purpose, ulong expectedCreateIntegrationVersion, AcDream.App.Rendering.LiveEntityAppearanceUpdateState? appearanceUpdate = null) => throw new InvalidOperationException( "The fixture pre-materializes the remote entity; " + "TryMaterialize should never be reached for an " + "already-projected accepted Position."); public void ResetSessionState() { } } private sealed class NoopRelationships : ILiveEntityRelationshipProjection { public void OnSpawn(WorldSession.EntitySpawn spawn) { } public void OnParent(ParentEvent.Parsed update) { } public void OnCreateParentAccepted(CreateParentUpdate update) { } public AcDream.App.Rendering.ChildUnparentDisposition OnChildBecameUnparented(uint childGuid) => AcDream.App.Rendering.ChildUnparentDisposition.NotAttached; public bool TryApplyAttachedAppearance( LiveEntityRecord record, ulong objDescAuthorityVersion) => false; } private sealed class NoopReadyPublisher : ILiveEntityReadyPublisher { public bool Publish(LiveEntityReadyCandidate candidate) => true; } private sealed class AlwaysKnownOrigin : ILiveEntityWorldOriginCoordinator { public bool IsKnown => true; public LiveEntityOriginInitialization TryInitialize( WorldSession.EntitySpawn spawn) => new(true, []); } private sealed class NoopNetworkSink : ILiveEntityNetworkUpdateSink { public void ApplySameGeneration(SameGenerationCreateObjectEvents events) { } } private sealed class NoopTimestampPublisher : IAcceptedLocalPhysicsTimestampPublisher { public void Publish(uint serverGuid, AcceptedPhysicsTimestamps timestamps) { } } private sealed class NoopDatReaderWriter : IDatReaderWriter { private readonly StubDatabase _portal = new(); private readonly StubDatabase _highRes = new(); private readonly StubDatabase _language = new(); private readonly StubDatabase _cell = new(); public string SourceDirectory => string.Empty; public IDatDatabase Portal => _portal; public IDatDatabase Cell => _cell; public ReadOnlyDictionary CellRegions { get; } = new(new Dictionary()); public IDatDatabase HighRes => _highRes; public IDatDatabase Language => _language; public IDatDatabase Local => _language; public ReadOnlyDictionary RegionFileMap { get; } = new(new Dictionary()); public int PortalIteration => 0; public int CellIteration => 0; public int HighResIteration => 0; public int LanguageIteration => 0; public bool TryGetFileBytes( uint regionId, uint fileId, ref byte[] bytes, out int bytesRead) { bytesRead = 0; return false; } public IEnumerable GetAllIdsOfType() where T : IDBObj => Array.Empty(); public IEnumerable ResolveId(uint id) => Array.Empty(); public bool TrySave(T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); public bool TrySave( uint regionId, T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); [return: MaybeNull] public T Get(uint fileId) where T : IDBObj => default; public bool TryGet( uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj { value = default; return false; } public void Dispose() { } } private sealed class StubDatabase : IDatDatabase { public DatDatabase Db => throw new NotSupportedException(); public int Iteration => 0; public IEnumerable GetAllIdsOfType() where T : IDBObj => Array.Empty(); public bool TryGet( uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj { value = default; return false; } public bool TryGetFileBytes( uint fileId, [MaybeNullWhen(false)] out byte[] value) { value = null; return false; } public bool TryGetFileBytes( uint fileId, ref byte[] bytes, out int bytesRead) { bytesRead = 0; return false; } public bool TrySave(T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); public void Dispose() { } } private sealed class NoopAnimationLoader : IAnimationLoader { public Animation? LoadAnimation(uint id) => null; } private sealed class NoopTeleportSink : ILocalPlayerTeleportNetworkSink { public void OnTeleportStarted(uint sequence) { } public void OfferDestination( RuntimeTeleportDestination destination, bool teleportTimestampAdvanced) { } public void OnLocalPlayerFirstEntryCompleted() { } public void ResetSession() { } public void ResetGenerationPresentation() { } } /// /// A makes /// ClassifyRemoteAcceptedPosition's player_distance /// input null, which /// /// then refuses outright (no fabricated distance) — silently /// starving every remote classification, including the teleport /// arm this fixture exists to exercise. A real (if origin-parked) /// controller supplies a finite distance instead. /// private sealed class StubLocalPlayerControllerSource : IRuntimeLocalPlayerControllerSource { public PlayerMovementController? Controller { get; } = new PlayerMovementController(new PhysicsEngine()); } /// /// Fix round (2026-08-04): the deliberate INVERSE of /// — a null /// starves player_distance, which /// makes ClassifyRemoteAcceptedPosition return a null route /// (the exact "login window" shape D2 exists for), rather than a /// real classification. Used ONLY by tests that specifically need a /// null-classified packet. /// private sealed class NoopLocalPlayerControllerSource : IRuntimeLocalPlayerControllerSource { public PlayerMovementController? Controller => null; } private sealed class NoopPhysicsHostSource : ILocalPlayerPhysicsHostSource { public EntityPhysicsHost? Host => null; } private sealed class FixedScriptTime : IPhysicsScriptTimeSource { public double CurrentScriptTime => 1_700_000_000d; } private sealed class NoopSessionSource : ILiveWorldSessionSource { public WorldSession? CurrentSession => null; } private sealed class NoopMovementTruthSink : IMovementTruthDiagnosticSink { public void OnOutbound( string kind, uint sequence, MovementResult result, Vector3 wirePosition, uint wireCellId, byte contactByte) { } public void OnServerEcho( WorldSession.EntityPositionUpdate update, Vector3 serverWorldPosition) { } public void ResetSession() { } } } }