using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime.Tests.Entities;
///
/// C4 route 4b-3 (D1): the load-bearing evidence chain for the whole slice —
/// measures the
/// PRE-merge committed cell (beforeCell/wasCellless) before
/// RefreshSnapshot stamps the accepted wire cell onto the canonical
/// record, and
/// must read THAT value, not the post-merge canonical.FullCellId every
/// other classifier caller reads. Reverting the fix (feeding the classifier
/// canonical.FullCellId after the merge instead of the pre-merge value)
/// must fail
/// — that is the ONE test that discriminates the fix from the shipped dead
/// predicate, because both predicates agree whenever the pre-merge cell truly
/// was zero.
///
public sealed class RuntimeRemoteTeleportClassificationTests
{
private const uint Cell = 0x0101FFFFu;
private const uint OtherCell = 0x0102FFFFu;
[Fact]
public void CellLessRecord_ClassifiesSetPosition_EvenWithoutATeleportAdvance()
{
using var lifetime = new RuntimeEntityObjectLifetime();
lifetime.BindEventContext(static () => new RuntimeGenerationToken(1), static () => 1UL);
const uint guid = 0x70005001u;
RuntimeEntityRecord canonical =
lifetime.RegisterEntity(Spawn(guid, Cell, instance: 1)).Canonical!;
Assert.Equal(Cell, canonical.FullCellId);
// The unwield-to-3D shape (AP-137's citation): a canonical withdrawal
// zeroes the committed cell without any wire packet.
lifetime.Entities.SetFullCell(canonical, 0u, 0u);
Assert.Equal(0u, canonical.FullCellId);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, OtherCell, positionSequence: 2, teleportSequence: 0);
Assert.True(lifetime.TryApplyPosition(
update,
isLocalPlayer: false,
forcePositionRotation: null,
currentLocalVelocity: null,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,
out AcceptedPhysicsTimestamps timestamps));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
// The measured pre-merge value is the honest 0 — not a fabrication,
// and not re-read after the merge (which would already show OtherCell).
Assert.Equal(0u, timestamps.PreMergeCommittedCellId);
Assert.False(timestamps.TeleportAdvanced);
Assert.True(lifetime.Entities.TryGetActive(guid, out RuntimeEntityRecord after));
RuntimeAuthoritativePositionRoute? route = lifetime.ClassifyRemoteAcceptedPosition(
after, update, disposition, timestamps, playerDistance: 10f);
Assert.NotNull(route);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPosition,
route!.Value.Disposition);
Assert.True(
(route.Value.SetPositionFlags & PhysicsSetPositionFlags.Teleport) != 0);
Assert.True(RuntimeRemoteTeleportPosition.OwnsTeleportPlacement(route));
}
///
/// The discriminator (contract's test plan item 4, companion half): the
/// SAME shape but WITHOUT the cell-less reset and WITHOUT a TELEPORT_TS
/// advance must NOT classify SetPosition. Under the shipped dead
/// predicate (reading canonical.FullCellId AFTER the merge) this
/// would ALSO pass, because the merge always stamps a nonzero wire cell —
/// the predicate was unreachable, not merely conservative. This test only
/// distinguishes the fix once
/// establishes the positive case is reachable at all.
///
[Fact]
public void CompanionTest_NonzeroPreMergeCellWithNoTeleportAdvance_DoesNotClassifySetPosition()
{
using var lifetime = new RuntimeEntityObjectLifetime();
lifetime.BindEventContext(static () => new RuntimeGenerationToken(1), static () => 1UL);
const uint guid = 0x70005002u;
RuntimeEntityRecord canonical =
lifetime.RegisterEntity(Spawn(guid, Cell, instance: 1)).Canonical!;
Assert.Equal(Cell, canonical.FullCellId);
// Deliberately NO SetFullCell(0, 0) — the record stays resident.
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, OtherCell, positionSequence: 2, teleportSequence: 0);
Assert.True(lifetime.TryApplyPosition(
update,
isLocalPlayer: false,
forcePositionRotation: null,
currentLocalVelocity: null,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,
out AcceptedPhysicsTimestamps timestamps));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
// Honest pre-merge value: still resident at the ORIGINAL cell, not
// the just-merged wire cell (which would be OtherCell) and not 0.
Assert.Equal(Cell, timestamps.PreMergeCommittedCellId);
Assert.False(timestamps.TeleportAdvanced);
Assert.True(lifetime.Entities.TryGetActive(guid, out RuntimeEntityRecord after));
RuntimeAuthoritativePositionRoute? route = lifetime.ClassifyRemoteAcceptedPosition(
after, update, disposition, timestamps, playerDistance: 10f);
Assert.NotNull(route);
Assert.NotEqual(
RuntimeAuthoritativePositionDisposition.SetPosition,
route!.Value.Disposition);
Assert.False(RuntimeRemoteTeleportPosition.OwnsTeleportPlacement(route));
}
///
/// A fresh TELEPORT_TS still classifies SetPosition even when the
/// pre-merge cell was resident — the OTHER half of retail's predicate
/// (newer_event(TELEPORT_TS) || this_1->cell == 0), unaffected by
/// D1's plumbing change.
///
[Fact]
public void FreshTeleportTimestamp_ClassifiesSetPosition_WithAResidentPreMergeCell()
{
using var lifetime = new RuntimeEntityObjectLifetime();
lifetime.BindEventContext(static () => new RuntimeGenerationToken(1), static () => 1UL);
const uint guid = 0x70005003u;
RuntimeEntityRecord canonical =
lifetime.RegisterEntity(Spawn(guid, Cell, instance: 1)).Canonical!;
Assert.Equal(Cell, canonical.FullCellId);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, OtherCell, positionSequence: 2, teleportSequence: 5);
Assert.True(lifetime.TryApplyPosition(
update,
isLocalPlayer: false,
forcePositionRotation: null,
currentLocalVelocity: null,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,
out AcceptedPhysicsTimestamps timestamps));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
Assert.Equal(Cell, timestamps.PreMergeCommittedCellId);
Assert.True(timestamps.TeleportAdvanced);
Assert.True(lifetime.Entities.TryGetActive(guid, out RuntimeEntityRecord after));
RuntimeAuthoritativePositionRoute? route = lifetime.ClassifyRemoteAcceptedPosition(
after, update, disposition, timestamps, playerDistance: 10f);
Assert.NotNull(route);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPosition,
route!.Value.Disposition);
Assert.True(RuntimeRemoteTeleportPosition.OwnsTeleportPlacement(route));
}
private static WorldSession.EntityPositionUpdate PositionUpdate(
uint guid,
uint cellId,
ushort positionSequence,
ushort teleportSequence) =>
new(
guid,
new CreateObject.ServerPosition(
cellId, 12f, 14f, 7f, 1f, 0f, 0f, 0f),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: positionSequence,
TeleportSequence: teleportSequence,
ForcePositionSequence: 0);
private static WorldSession.EntitySpawn Spawn(
uint guid,
uint cellId,
ushort instance)
{
var position = new CreateObject.ServerPosition(
cellId, 10f, 20f, 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: instance);
var physics = new PhysicsSpawnData(
RawState: 0x408u,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: 0x09000001u,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
0x02000001u,
Array.Empty(),
Array.Empty(),
Array.Empty(),
null,
null,
"remote-teleport-classification",
null,
null,
0x09000001u,
PhysicsState: 0x408u,
InstanceSequence: instance,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
}