using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
namespace AcDream.Runtime.Tests.Entities;
///
/// C4 route 7 (pickup / parent / delete). Focused Runtime tests for D1
/// (attach re-cell), D2 (crossing propagation at the directory funnel), D3
/// (withdrawal/delete/EndGeneration edges), and D7 (pickup ordering). See
/// docs/research/2026-08-04-c4-route-7-contract.md.
///
public sealed class RuntimeEntityChildCellPropagationTests
{
private const uint Landblock = 0xA9C60000u;
private const uint Cell = Landblock | 0x0001u;
[Fact]
public void Attach_ParentCelled_ChildEndsAtParentCellAndStaysSuspended()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040001u;
const uint childGuid = 0x70040002u;
RuntimeEntityRecord parent =
lifetime.RegisterEntity(Spawn(parentGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
// A4 (architecture review): D1's re-cell runs BEFORE this commit's
// publish, so the Withdrawn delta it emits carries the child's
// ACTUAL resulting (non-zero, parent) cell rather than the stale
// zero every Withdrawn from this method carried before D1 existed.
var deltas = new List();
using IDisposable subscription = lifetime.Events.Subscribe(
new RecordingEntityObserver(deltas));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
// D1: the child ends at the parent's exact cell in the SAME
// synchronous transaction — never observably cell-less afterward.
Assert.Equal(parent.FullCellId, child.FullCellId);
Assert.Equal(parent.CanonicalLandblockId, child.CanonicalLandblockId);
Assert.NotEqual(0u, child.FullCellId);
// Invariant 2 / P2: the child is parent-suspended, never a
// self-simulating object (retail update_object @0x00515D40).
Assert.False(child.ObjectClock.IsActive);
Assert.Null(child.Snapshot.Position);
RuntimeEntityDelta withdrawn = Assert.Single(
deltas,
d => d.Change is RuntimeEntityChange.Withdrawn
&& d.Entity.Identity.ServerGuid == childGuid);
Assert.Equal(parent.FullCellId, withdrawn.Entity.CellId);
Assert.NotEqual(0u, withdrawn.Entity.CellId);
}
private sealed class RecordingEntityObserver(List destination)
: IRuntimeEntityObjectObserver
{
public void OnEntity(in RuntimeEntityDelta delta) => destination.Add(delta);
public void OnInventory(in RuntimeInventoryDelta delta)
{
}
}
[Fact]
public void Attach_ParentCellless_ChildStaysCellless_ThenLaterParentCellCommitRecellsViaPropagation()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040101u;
const uint childGuid = 0x70040102u;
lifetime.RegisterEntity(Spawn(parentGuid, 1, includePosition: false));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
parentGuid, out RuntimeEntityRecord parent));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.Equal(0u, parent.FullCellId);
// @0x00515AD1: parent->cell == 0 -> the child stays cell-less too.
Assert.Equal(0u, child.FullCellId);
// The deferred-attach catch-up retail gets for free from
// propagation: a LATER parent cell commit re-cells the child
// through D2, with no separate mechanism.
Assert.True(lifetime.CommitRebucket(parent, Cell, Landblock | 0xFFFFu));
Assert.Equal(Cell, child.FullCellId);
}
[Fact]
public void PropagationChokepoint_RecursesThroughGrandchildAndIsIdempotentOnASameCellCommit()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040201u;
const uint childGuid = 0x70040202u;
const uint grandchildGuid = 0x70040203u;
RuntimeEntityRecord parent =
lifetime.RegisterEntity(Spawn(parentGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
lifetime.RegisterEntity(Spawn(grandchildGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(CommitAttachment(lifetime, childGuid, 1, grandchildGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.Entities.TryGetActive(
grandchildGuid, out RuntimeEntityRecord grandchild));
Assert.Equal(parent.FullCellId, child.FullCellId);
Assert.Equal(parent.FullCellId, grandchild.FullCellId);
// The production writer (CommitRebucket) crosses the parent's cell.
uint newCell = parent.FullCellId + 0x0002u;
uint newLandblock = (newCell & 0xFFFF0000u) | 0xFFFFu;
Assert.True(lifetime.CommitRebucket(parent, newCell, newLandblock));
Assert.Equal(newCell, child.FullCellId);
// Unbounded-depth recursion: the grandchild follows too.
Assert.Equal(newCell, grandchild.FullCellId);
// Idempotence: a same-cell re-commit does not churn the child's
// SpatialAuthorityVersion (D2's explicit short-circuit).
ulong childVersionBefore = child.SpatialAuthorityVersion;
ulong grandchildVersionBefore = grandchild.SpatialAuthorityVersion;
Assert.True(lifetime.CommitRebucket(parent, newCell, newLandblock));
Assert.Equal(childVersionBefore, child.SpatialAuthorityVersion);
Assert.Equal(grandchildVersionBefore, grandchild.SpatialAuthorityVersion);
}
[Fact]
public void PropagationChokepoint_TerminatesAHostileTwoCycleInsteadOfLoopingForever()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint aGuid = 0x70040301u;
const uint bGuid = 0x70040302u;
RuntimeEntityRecord a =
lifetime.RegisterEntity(Spawn(aGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(bGuid, 1, includePosition: false));
// A hostile wire commits BOTH directions: B is A's child, and A is
// ALSO B's child (a relation cycle no single-relation check like
// self-parenting rejection catches).
Assert.True(CommitAttachment(lifetime, aGuid, 1, bGuid, 2));
Assert.True(CommitAttachment(lifetime, bGuid, 1, aGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(bGuid, out RuntimeEntityRecord b));
uint newCell = a.FullCellId + 0x0002u;
// Must terminate (not stack-overflow / infinite-loop) and still
// propagate once around the cycle.
Assert.True(lifetime.CommitRebucket(a, newCell, (newCell & 0xFFFF0000u) | 0xFFFFu));
Assert.Equal(newCell, a.FullCellId);
Assert.Equal(newCell, b.FullCellId);
}
[Fact]
public void RefreshSnapshot_WireMergeCellChange_PropagatesToCommittedChild()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040401u;
const uint childGuid = 0x70040402u;
RuntimeEntityRecord parent =
lifetime.RegisterEntity(Spawn(parentGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.Equal(parent.FullCellId, child.FullCellId);
WorldSession.EntitySpawn moved = parent.Snapshot with
{
Position = parent.Snapshot.Position!.Value with
{
LandblockId = parent.Snapshot.Position!.Value.LandblockId + 0x0002u,
},
};
lifetime.Entities.RefreshSnapshot(parent, moved, refreshPosition: true);
Assert.NotEqual(0u, parent.FullCellId);
Assert.Equal(parent.FullCellId, child.FullCellId);
}
[Fact]
public void P1_SnapshotMutationOfACommittedChild_LeavesItsCanonicalCellTrackingTheParent()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040501u;
const uint childGuid = 0x70040502u;
RuntimeEntityRecord parent =
lifetime.RegisterEntity(Spawn(parentGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
uint expectedCell = parent.FullCellId;
// A representative snapshot-mutation family: ObjDesc. The child's
// Snapshot.Position stays null (contract §0 item 10), so
// RefreshDerivedState's cell stamp can never fire from the child's
// own merge and cannot fight the propagation.
var objDesc = new ObjDescEvent.Parsed(
childGuid,
new CreateObject.ModelData(
BasePaletteId: null,
SubPalettes: [],
TextureChanges: [],
AnimPartChanges: []),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(
objDesc, acknowledgeProjection: null, out _));
Assert.Null(child.Snapshot.Position);
Assert.Equal(expectedCell, child.FullCellId);
}
[Fact]
public void Withdrawal_PickupOfParent_ZeroesCommittedChildrenRecursively()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040601u;
const uint childGuid = 0x70040602u;
const uint grandchildGuid = 0x70040603u;
lifetime.RegisterEntity(Spawn(parentGuid, 1));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
lifetime.RegisterEntity(Spawn(grandchildGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(CommitAttachment(lifetime, childGuid, 1, grandchildGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.Entities.TryGetActive(
grandchildGuid, out RuntimeEntityRecord grandchild));
Assert.NotEqual(0u, child.FullCellId);
Assert.NotEqual(0u, grandchild.FullCellId);
Assert.True(lifetime.TryApplyPickup(
new PickupEvent.Parsed(parentGuid, InstanceSequence: 1, PositionSequence: 2),
acknowledgeProjection: null,
out _));
Assert.Equal(0u, child.FullCellId);
Assert.Equal(0u, grandchild.FullCellId);
}
///
/// #319 route-7 invariant 2 re-run, dual-parent-class. The propagation
/// mechanism itself (D1/D2/D3) was always guid-range-agnostic - these
/// tests construct relations directly via
/// rather than through the buggy producer - but #319 exists precisely
/// because no existing test in this file used a PLAYER-range parent
/// guid. Closes that gap for the removal path (AP-142 clause (a)): the
/// key fix must not perturb withdrawal for either parent class.
///
[Theory]
[InlineData(0x50000601u, (ushort)8)]
[InlineData(0x70040609u, (ushort)0)]
public void Withdrawal_PickupOfParent_ZeroesCommittedChildren_DualParentClass(
uint parentGuid,
ushort parentInstance)
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint childGuid = 0x7004060Au;
lifetime.RegisterEntity(Spawn(parentGuid, parentInstance));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, parentInstance, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.NotEqual(0u, child.FullCellId);
Assert.True(lifetime.TryApplyPickup(
new PickupEvent.Parsed(parentGuid, InstanceSequence: parentInstance, PositionSequence: 2),
acknowledgeProjection: null,
out _));
Assert.Equal(0u, child.FullCellId);
}
[Fact]
public void Withdrawal_CommitWithdrawalOfParent_ZeroesCommittedChildren()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040701u;
const uint childGuid = 0x70040702u;
RuntimeEntityRecord parent =
lifetime.RegisterEntity(Spawn(parentGuid, 1)).Canonical!;
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.NotEqual(0u, child.FullCellId);
Assert.True(lifetime.CommitWithdrawal(parent));
Assert.Equal(0u, parent.FullCellId);
Assert.Equal(0u, child.FullCellId);
}
[Fact]
public void Delete_ZeroesChildrenBeforeRelationsAreTornDown_P7()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040801u;
const uint childGuid = 0x70040802u;
const uint grandchildGuid = 0x70040803u;
lifetime.RegisterEntity(Spawn(parentGuid, 1));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
lifetime.RegisterEntity(Spawn(grandchildGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(CommitAttachment(lifetime, childGuid, 1, grandchildGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.Entities.TryGetActive(
grandchildGuid, out RuntimeEntityRecord grandchild));
Assert.NotEqual(0u, child.FullCellId);
Assert.NotEqual(0u, grandchild.FullCellId);
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
// The children's own leave-world edge ran before DeleteGeneration
// removed the relation ledger (P7) — both the direct and the
// grand-attached child went cell-less. DeleteObject unparents only
// the deleted object's DIRECT children (retail unparent_children):
// the child's own relation to the deleted parent is gone, but the
// grandchild's relation to the (still-alive, merely cell-less)
// child is untouched — exactly retail's shape.
Assert.Equal(0u, child.FullCellId);
Assert.Equal(0u, grandchild.FullCellId);
Assert.False(lifetime.Entities.ParentAttachments.HasCommittedParent(childGuid));
Assert.True(lifetime.Entities.ParentAttachments.HasCommittedParent(grandchildGuid));
}
[Fact]
public void EndGeneration_ReplacementParentGeneration_ZeroesFormerlyCommittedChildren()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040901u;
const uint childGuid = 0x70040902u;
lifetime.RegisterEntity(Spawn(parentGuid, 1));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.NotEqual(0u, child.FullCellId);
// A new CreateObject generation for the SAME parent guid — the
// replacement-generation path has the same stranding shape as
// delete (D3).
lifetime.RegisterEntity(Spawn(parentGuid, 2));
Assert.Equal(0u, child.FullCellId);
Assert.False(lifetime.Entities.ParentAttachments.HasCommittedParent(childGuid));
}
[Fact]
public void PickupOfTheChildItself_D7_RelationGoneCellZeroClockSuspendedAndOwnChildrenAlsoCellless()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040A01u;
const uint childGuid = 0x70040A02u;
const uint grandchildGuid = 0x70040A03u;
lifetime.RegisterEntity(Spawn(parentGuid, 1));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
lifetime.RegisterEntity(Spawn(grandchildGuid, 1, includePosition: false));
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(CommitAttachment(lifetime, childGuid, 1, grandchildGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.Entities.TryGetActive(
grandchildGuid, out RuntimeEntityRecord grandchild));
// Pick up the CHILD itself (not the parent) — retail's
// unset_parent-then-leave_world order (D7). PositionSequence 3:
// the attach commit already consumed 2.
Assert.True(lifetime.TryApplyPickup(
new PickupEvent.Parsed(childGuid, InstanceSequence: 1, PositionSequence: 3),
acknowledgeProjection: null,
out _));
Assert.False(lifetime.Entities.ParentAttachments.HasCommittedParent(childGuid));
Assert.Equal(0u, child.FullCellId);
Assert.False(child.ObjectClock.IsActive);
// The picked-up child's OWN children went cell-less too — the
// pickup's SetFullCell(0,0) is a value like any other at D2's
// chokepoint.
Assert.Equal(0u, grandchild.FullCellId);
}
[Fact]
public void NeverArmPartition_D8_RouteSevenEventsNeverEngagePlacementOrParkMachinery()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const uint parentGuid = 0x70040B01u;
const uint childGuid = 0x70040B02u;
lifetime.RegisterEntity(Spawn(parentGuid, 1));
lifetime.RegisterEntity(Spawn(childGuid, 1, includePosition: false));
int placementOpsBefore =
lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount;
Assert.True(CommitAttachment(lifetime, parentGuid, 1, childGuid, 2));
Assert.True(lifetime.Entities.TryGetActive(
parentGuid, out RuntimeEntityRecord parent));
for (int i = 0; i < 5; i++)
{
uint nextCell = parent.FullCellId + 0x0002u;
Assert.True(lifetime.CommitRebucket(
parent, nextCell, (nextCell & 0xFFFF0000u) | 0xFFFFu));
}
Assert.True(lifetime.TryApplyPickup(
new PickupEvent.Parsed(childGuid, InstanceSequence: 1, PositionSequence: 3),
acknowledgeProjection: null,
out _));
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
// No placement, park, or ConstrainTo machinery engaged at any point
// (P3 / D8): attach, five crossings, pickup, and delete leave the
// placement operation ledger exactly where it started.
Assert.Equal(
placementOpsBefore,
lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
// Ledger convergence (invariant 13): both relations are gone.
Assert.Equal(
0,
lifetime.CaptureOwnership().CommittedParentRelationCount);
}
///
/// Round-3 remediation: the recursion + depth cap were replaced outright
/// by an iterative worklist (both the retail and architecture reviews
/// independently found the same defect — a capped recursive version
/// left a truncated tail at a STALE NONZERO cell forever, the #184
/// shape verbatim, on the withdraw path). Builds a chain well beyond
/// the OLD 64-level cap (200 nodes) and proves the ENTIRE chain — not
/// just a prefix — follows the parent on BOTH a write (crossing) and a
/// withdraw (zero) with no truncation and no
/// .
///
[Fact]
public void PropagationChokepoint_DeepChain_FullyPropagatesOnBothWriteAndWithdraw()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
const int chainLength = 200; // well beyond the retired 64-level cap
var guids = new uint[chainLength + 1];
for (int i = 0; i <= chainLength; i++)
guids[i] = 0x70050000u + (uint)i;
lifetime.RegisterEntity(Spawn(guids[0], 1));
for (int i = 1; i <= chainLength; i++)
lifetime.RegisterEntity(Spawn(guids[i], 1, includePosition: false));
for (int i = 0; i < chainLength; i++)
{
Assert.True(CommitAttachment(
lifetime, guids[i], 1, guids[i + 1], childPositionSequence: 2));
}
Assert.True(lifetime.Entities.TryGetActive(
guids[0], out RuntimeEntityRecord root));
// D1's attach re-cell already propagated the ORIGINAL cell down the
// whole chain one attach at a time — confirm the tail matches the
// root before testing the crossing, so the assertions below cannot
// pass by coincidence.
uint originalCell = root.FullCellId;
Assert.True(lifetime.Entities.TryGetActive(
guids[chainLength], out RuntimeEntityRecord tailBeforeCrossing));
Assert.Equal(originalCell, tailBeforeCrossing.FullCellId);
Assert.NotEqual(0u, originalCell);
// WRITE path: the whole 200-deep chain follows a crossing, tail
// included — no truncation, no stack overflow.
uint newCell = originalCell + 0x0002u;
Assert.True(lifetime.CommitRebucket(
root, newCell, (newCell & 0xFFFF0000u) | 0xFFFFu));
for (int i = 0; i <= chainLength; i++)
{
Assert.True(lifetime.Entities.TryGetActive(
guids[i], out RuntimeEntityRecord record));
Assert.Equal(newCell, record.FullCellId);
}
// WITHDRAW path: the whole chain follows a withdrawal to zero too —
// this is the #184-shaped half both reviews flagged (a truncated
// tail left at a STALE NONZERO cell is exactly the "resident but
// isn't" defect AP-142 clause (a) exists to reject).
Assert.True(lifetime.CommitWithdrawal(root));
for (int i = 0; i <= chainLength; i++)
{
Assert.True(lifetime.Entities.TryGetActive(
guids[i], out RuntimeEntityRecord record));
Assert.Equal(0u, record.FullCellId);
}
}
// ---------------------------------------------------------------
// Harness
// ---------------------------------------------------------------
private static RuntimeEntityObjectLifetime EngineLifetime()
{
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty(),
Array.Empty(),
worldOffsetX: 0f,
worldOffsetY: 0f);
return new RuntimeEntityObjectLifetime(engine);
}
private static void Bind(RuntimeEntityObjectLifetime lifetime, ulong generation)
{
var token = new RuntimeGenerationToken(generation);
lifetime.BindEventContext(() => token, static () => 1UL);
}
///
/// Drives the SAME staged -> committed protocol the graphical
/// EquippedChildRenderController.PrepareAndTryRealize and the
/// headless D5 drive both run: seed the staged relation, consume the
/// child's POSITION_TS gate, commit the relation
/// (), mark it
/// committed (),
/// then run the cell-less edge that D1 extends
/// ().
///
private static bool CommitAttachment(
RuntimeEntityObjectLifetime lifetime,
uint parentGuid,
ushort parentInstance,
uint childGuid,
ushort childPositionSequence,
uint parentLocation = 0u,
uint placementId = 0u)
{
var relation = new ParentAttachmentRelation(
parentGuid,
childGuid,
parentLocation,
placementId,
parentInstance,
childPositionSequence);
lifetime.Entities.ParentAttachments.AcceptCreateObjectRelation(relation);
var update = new ParentEvent.Parsed(
parentGuid,
childGuid,
parentLocation,
placementId,
parentInstance,
childPositionSequence);
if (!lifetime.TryApplyParent(update, acknowledgeProjection: null, out _))
return false;
if (!lifetime.TryCommitParent(relation, acknowledgeProjection: null, out _))
return false;
if (!lifetime.Entities.ParentAttachments.CommitProjection(relation))
return false;
if (!lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord canonical))
return false;
return lifetime.CommitAcceptedParentCellless(
canonical,
canonical.PositionAuthorityVersion,
acknowledgeProjection: null);
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort incarnation,
bool includePosition = true)
{
CreateObject.ServerPosition? position = includePosition
? new CreateObject.ServerPosition(Cell, 10f, 20f, 7f, 1f, 0f, 0f, 0f)
: null;
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.Gravity,
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,
SetupTableId: null,
Array.Empty(),
Array.Empty(),
Array.Empty(),
BasePaletteId: null,
ObjScale: null,
Name: "route-7 fixture",
ItemType: null,
MotionState: null,
MotionTableId: null,
PhysicsState: physics.RawState,
InstanceSequence: incarnation,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
}