using System.Collections; using System.Numerics; using System.Reflection; using AcDream.App.Rendering; using AcDream.App.Rendering.Vfx; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Content; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using AcDream.Runtime.Entities; using DatReaderWriter.Types; namespace AcDream.App.Tests.Rendering; public sealed class EquippedChildProjectionWithdrawalTests { [Fact] public void WithdrawalFailure_DoesNotPublishAttachedProjectionRemoval() { bool published = false; ExactProjectionWithdrawalOutcome outcome = EquippedChildRenderController.WithdrawAttachedProjection( ChildRecord(), positionAuthorityVersion: 1, projectionMutationVersion: 2, (_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("injected component cleanup failure")), () => { published = true; return true; }); Assert.IsType(outcome.Failure); Assert.Equal(ExactProjectionWithdrawalDisposition.Pending, outcome.Disposition); Assert.False(published); } [Fact] public void ExactIncarnationRejection_DoesNotPublishAttachedProjectionRemoval() { bool published = false; ExactProjectionWithdrawalOutcome outcome = EquippedChildRenderController.WithdrawAttachedProjection( ChildRecord(), positionAuthorityVersion: 1, projectionMutationVersion: 2, (_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Superseded, Failure: null), () => { published = true; return true; }); Assert.True(published); Assert.Equal(ExactProjectionWithdrawalDisposition.Superseded, outcome.Disposition); } [Fact] public void SuccessfulWithdrawal_PublishesLocalProjectionRemovalOnce() { int publications = 0; ExactProjectionWithdrawalOutcome outcome = EquippedChildRenderController.WithdrawAttachedProjection( ChildRecord(), positionAuthorityVersion: 1, projectionMutationVersion: 2, (_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null), () => { publications++; return true; }); Assert.Equal(ExactProjectionWithdrawalDisposition.Completed, outcome.Disposition); Assert.Equal(1, publications); } [Fact] public void LogicalReplacement_RemovesExactAttachedMapWithoutWithdrawingReplacement() { int withdrawals = 0; using var fixture = new ControllerFixture( (_, _, _) => { withdrawals++; return new( ExactProjectionWithdrawalDisposition.Completed, Failure: null); }); LiveEntityRecord parent = fixture.Spawn(0x70000200u, generation: 1); LiveEntityRecord oldChild = fixture.Spawn( 0x70000201u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, oldChild); LiveEntityRecord replacement = fixture.Live.RegisterAndMaterializeProjection( ControllerFixture.SpawnData(0x70000201u, generation: 2)); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.True(fixture.Live.TryGetRecord(0x70000201u, out LiveEntityRecord current)); Assert.Same(replacement, current); Assert.Equal(0, withdrawals); } [Fact] public void LogicalDelete_NotificationFailureLeavesRetryableTombstoneButNoAttachedLeak() { using var fixture = new ControllerFixture((_, _, _) => new(ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(0x70000210u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000211u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.Controller.ProjectionRemoved += Throw; Assert.Throws(() => fixture.Live.UnregisterLiveEntity( new DeleteObject.Parsed(0x70000211u, InstanceSequence: 1), isLocalPlayer: false)); Assert.Empty(fixture.Controller.AttachedEntityIds); fixture.Controller.ProjectionRemoved -= Throw; Assert.Equal(1, fixture.Live.RetryPendingTeardowns()); static void Throw(LiveEntityRecord _) => throw new InvalidOperationException("injected projection observer failure"); } [Fact] public void PostCommitWithdrawalFailure_RetiresCapturedAttachedMap() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool committed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); Assert.True(committed); return new( ExactProjectionWithdrawalDisposition.Completed, new InvalidOperationException("observer failed after commit")); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x70000220u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000221u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); Assert.Throws(() => fixture.Controller.OnChildBecameUnparented(0x70000221u)); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.False(child.IsSpatiallyProjected); } } [Fact] public void PendingTopLevelWithdrawal_RetryRunsAcceptedContinuationOnce() { int attempts = 0; ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { attempts++; if (attempts == 1) { return new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("component cleanup failed")); } bool committed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( committed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord record = fixture.Spawn(0x70000230u, generation: 1); int continuations = 0; Assert.Throws(() => fixture.Controller.OnChildBecameUnparented( 0x70000230u, () => { continuations++; Assert.True(fixture.Live.RebucketLiveEntity( 0x70000230u, 0x01010001u)); })); fixture.Controller.Tick(); Assert.Equal(2, attempts); Assert.Equal(1, continuations); Assert.True(record.IsSpatiallyProjected); } } [Fact] public void PendingUnparent_NewerPositionSupersedesRecoveryContinuation() { using var fixture = new ControllerFixture((_, _, _) => new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("component cleanup failed"))); LiveEntityRecord record = fixture.Spawn(0x70000240u, generation: 1); int continuations = 0; Assert.Throws(() => fixture.Controller.OnChildBecameUnparented( 0x70000240u, () => continuations++)); var newer = new WorldSession.EntityPositionUpdate( 0x70000240u, new CreateObject.ServerPosition( 0x01010001u, 4f, 5f, 6f, 1f, 0f, 0f, 0f), Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 0); Assert.True(fixture.Live.TryApplyPosition( newer, isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out _, out _, out _)); fixture.Controller.Tick(); Assert.Equal(0, continuations); Assert.True(record.IsSpatiallyProjected); } [Fact] public void PostNetworkReconcile_SkipsStableTree_AndUpdatesChangedBranchParentFirst() { using var fixture = new ControllerFixture((_, _, _) => new( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord root = fixture.Spawn(0x70000241u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000242u, generation: 1, LiveEntityProjectionKind.Attached); LiveEntityRecord grandchild = fixture.Spawn( 0x70000243u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(root, child); fixture.InstallAttached(child, grandchild); fixture.Poses.Publish(root.WorldEntity!, Array.Empty()); fixture.Controller.Tick(); Assert.Equal(2, fixture.Controller.LastFullPoseCompositionVisits); fixture.Controller.ReconcileSpatialMutations(); Assert.Equal(0, fixture.Controller.LastReconcilePoseCompositionVisits); root.WorldEntity!.SetPosition(new Vector3(7f, 8f, 9f)); Assert.True(fixture.Poses.UpdateRoot(root.WorldEntity)); fixture.Controller.ReconcileSpatialMutations(); Assert.Equal(2, fixture.Controller.LastReconcilePoseCompositionVisits); Assert.Equal(root.WorldEntity.Position, child.WorldEntity!.Position); Assert.Equal(child.WorldEntity.Position, grandchild.WorldEntity!.Position); } [Fact] public void StablePostNetworkReconcile_AllocatesNoTransitionSnapshots() { using var fixture = new ControllerFixture((_, _, _) => new( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord root = fixture.Spawn(0x70000244u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000245u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(root, child); fixture.Poses.Publish(root.WorldEntity!, Array.Empty()); fixture.Controller.Tick(); fixture.Controller.ReconcileSpatialMutations(); // #250: the measured window was a 1,000-iteration loop written inline. ZeroAllocationProbe.AssertAllocatesNothing( "LiveEntityController.ReconcileSpatialMutations", () => fixture.Controller.ReconcileSpatialMutations()); Assert.Equal(0, fixture.Controller.LastReconcilePoseCompositionVisits); } /// /// C4 route 7 D4 (review round 2, A1/R2 remediation): TickChild's /// rebucket is now presentation-only — Runtime's D1/D2 propagation is /// the canonical writer. Drives the parent's canonical cell change /// through a Runtime producer (CommitRebucket, standing in for /// any of D2's four writer families) BEFORE any render tick runs, /// proving the canonical half needs no tick at all; then ticks and /// proves the DRAW BUCKET itself moved — the actual /// GpuWorldState spatial index membership, not the /// ParentCellId mirror field (which TickChild writes /// unconditionally before the rebucket call and therefore proves /// nothing about it — the original review's A1 finding). Both /// landblocks are registered so membership is checked by an actual /// per-landblock query (CopyLiveEntitiesNearLandblock), not by a /// field read. /// [Fact] public void TickChild_D4_PresentationBucketMovesToTheDestinationLandblock() { using var fixture = new ControllerFixture((_, _, _) => new(ExactProjectionWithdrawalDisposition.Completed, Failure: null)); const uint oldLandblock = 0x0101FFFFu; const uint newCell = 0x01020001u; const uint newLandblock = 0x0102FFFFu; fixture.Spatial.AddLandblock(new LoadedLandblock( newLandblock, new LandBlock(), Array.Empty())); LiveEntityRecord parent = fixture.Spawn(0x70000260u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000261u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); var relation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); fixture.CommitRenderedRelation(relation); // Sanity: the child's draw bucket starts in the OLD landblock (set // by materialization), not the new one — equality after the tick // cannot be coincidental. var buffer = new List>(); fixture.Spatial.CopyLiveEntitiesNearLandblock(oldLandblock, 0, buffer); Assert.Contains(buffer, kv => kv.Value == child.WorldEntity); fixture.Spatial.CopyLiveEntitiesNearLandblock(newLandblock, 0, buffer); Assert.DoesNotContain(buffer, kv => kv.Value == child.WorldEntity); // D2: the parent's canonical cell write propagates to the // committed child's canonical cell with NO render tick involved. Assert.True(fixture.EntityObjects.CommitRebucket( parent.Canonical, newCell, newLandblock)); Assert.Equal(newCell, child.Canonical.FullCellId); ulong childSpatialVersionBeforeTick = child.Canonical.SpatialAuthorityVersion; // The parent's OWN presentation already followed its canonical // cell (a route-7-unrelated concern) so TickChild's pose loop reads // the same destination; give the child a stale draw bucket first. parent.WorldEntity!.ParentCellId = newCell; child.WorldEntity!.ParentCellId = 0x01010001u; fixture.Controller.Tick(); // D4: the render tick moved the actual spatial bucket — the child // left the OLD landblock's live-entity set and joined the NEW one. fixture.Spatial.CopyLiveEntitiesNearLandblock(newLandblock, 0, buffer); Assert.Contains(buffer, kv => kv.Value == child.WorldEntity); fixture.Spatial.CopyLiveEntitiesNearLandblock(oldLandblock, 0, buffer); Assert.DoesNotContain(buffer, kv => kv.Value == child.WorldEntity); // ...and did not re-write the canonical cell. Assert.Equal( childSpatialVersionBeforeTick, child.Canonical.SpatialAuthorityVersion); } /// /// C4 route 7 D4 (review round 2, A2/R3 remediation; round-3 review /// B1/B2/N5 fix): the KNOWN-benign decline. The committed relation can /// unwind (pickup / Position-unparent) while _attachedByChild /// still holds the child, ahead of OnChildBecameUnparented's own /// teardown. TickChild must not silently discard that disposition and /// must not move the draw bucket on a write-nothing outcome, but the /// tick itself still "succeeds" (the pose composed and published) /// rather than being torn down as a pose-loss failure. /// /// /// CORRECTED (B1/B2/N5, round-3 review): the first round's "bucket /// didn't move" assertion was non-discriminating — the parent's cell /// never changed in that version, so the child's bucket would have /// stayed in the same landblock whether or not the NotAttached guard /// fired at all. This version moves the PARENT's canonical cell to a /// real second landblock BEFORE removing the relation, so if the guard /// were bypassed the child WOULD move there — the assertion can now /// actually fail. /// /// [Fact] public void TickChild_D4_NotAttachedDisposition_SkipsTheBucketMoveWithoutFailingTheTick() { using var fixture = new ControllerFixture((_, _, _) => new(ExactProjectionWithdrawalDisposition.Completed, Failure: null)); const uint oldLandblock = 0x0101FFFFu; const uint newCell = 0x01020001u; const uint newLandblock = 0x0102FFFFu; fixture.Spatial.AddLandblock(new LoadedLandblock( newLandblock, new LandBlock(), Array.Empty())); LiveEntityRecord parent = fixture.Spawn(0x70000262u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000263u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); var relation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); fixture.CommitRenderedRelation(relation); // A real destination now exists: move the parent's canonical cell // (D2 propagates it to the still-committed child too) and its // presentation field, so TickChild's pose loop WOULD send the // child to newLandblock if the NotAttached guard did not fire. Assert.True(fixture.EntityObjects.CommitRebucket( parent.Canonical, newCell, newLandblock)); parent.WorldEntity!.ParentCellId = newCell; // Runtime already unwound the relation (pickup/Position-unparent), // but the App's _attachedByChild still holds the child (the // pre-OnChildBecameUnparented window A2 names). Assert.True(fixture.Live.ParentAttachments.HasCommittedParent( child.ServerGuid)); fixture.Live.ParentAttachments.EndChildProjection(child.ServerGuid); Assert.False(fixture.Live.ParentAttachments.HasCommittedParent( child.ServerGuid)); int visitsBefore = fixture.Controller.LastFullPoseCompositionVisits; fixture.Controller.Tick(); // The tick still ran and composed the pose (not treated as pose // loss)... Assert.Equal(visitsBefore + 1, fixture.Controller.LastFullPoseCompositionVisits); Assert.Contains(child.WorldEntity!.Id, fixture.Controller.AttachedEntityIds); // ...but the draw bucket did NOT move to the new landblock — a real // destination existed and the guard correctly declined // (NotAttached), so RebucketLiveEntityPresentationOnly never ran. var buffer = new List>(); fixture.Spatial.CopyLiveEntitiesNearLandblock(oldLandblock, 0, buffer); Assert.Contains(buffer, kv => kv.Value == child.WorldEntity); fixture.Spatial.CopyLiveEntitiesNearLandblock(newLandblock, 0, buffer); Assert.DoesNotContain(buffer, kv => kv.Value == child.WorldEntity); } /// /// C4 route 7 D4 (round-3 review, N6): the OTHER new disposition value /// — . /// /// /// CORRECTED (round-3 sabotage pass): the first version of this test /// called Controller.Tick() and asserted ProjectionPoseReady /// did not fire, expecting to exercise TickChild's /// if (disposition is NoProjection) return false; guard at /// EquippedChildRenderController.cs:419-427. Sabotaging that exact /// guard (forcing the branch to be skipped) did NOT break the test — /// investigation showed why: TickChild's own top-of-method gate, /// TryResolveExactAttachment (called at both /// EquippedChildRenderController.cs:379 and again at :412 /// immediately before the rebucket call), requires /// _liveEntities.IsCurrentRecord(child.ChildRecord) to hold — and /// IsCurrentRecord (LiveEntityRuntime.cs:3336-3338) is /// itself defined as _projections.TryGetCurrent(guid, out current) && /// ReferenceEquals(current, record) — the EXACT SAME store lookup /// RebucketEquippedChildPresentation's own NoProjection /// guard (LiveEntityRuntime.cs:1165) performs for the same guid, /// one call later with nothing in between that could invalidate it. /// Whenever RebucketEquippedChildPresentation would see no current /// projection, TryResolveExactAttachment already failed first and /// TickChild already returned at /// :442, never reaching the rebucket call at all. The /// NoProjection check inside RebucketEquippedChildPresentation /// is therefore defensive/unreachable from this call site as currently /// structured, not a live withdrawal trigger — left in place because it /// is the correct contract for the method taken on its own (any other /// caller, or a future refactor that removes/reorders the /// TryResolveExactAttachment gate, could reach it), but it is not /// exercisable end-to-end through TickChild today. /// /// /// /// This test therefore calls RebucketEquippedChildPresentation /// directly — the same technique already used by /// ControllerFixture.RemoveProjectionOnly to reach the abnormal /// state — proving the disposition VALUE is correct in isolation, rather /// than asserting a TickChild-level trigger that does not exist. The /// earlier "pose-loss recovery also no-ops here" finding (reported, not /// fixed, in the prior revision of this comment) is superseded by this /// finding: since TickChild never reaches NoProjection from this /// state, WithdrawForPoseLoss's interaction with it is moot. /// /// [Fact] public void RebucketEquippedChildPresentation_D4_NoLiveProjection_ReturnsNoProjectionDisposition() { using var fixture = new ControllerFixture((_, _, _) => new(ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(0x70000264u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000265u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); var relation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); fixture.CommitRenderedRelation(relation); // The relation stays committed (unlike the NotAttached test) — only // the projection itself vanishes, which is the ABNORMAL case // NoProjection exists to catch. Assert.True(fixture.Live.ParentAttachments.HasCommittedParent( child.ServerGuid)); fixture.RemoveProjectionOnly(child); EquippedChildPresentationRebucketDisposition disposition = fixture.Live.RebucketEquippedChildPresentation( child.ServerGuid, 0x01010001u); Assert.Equal( EquippedChildPresentationRebucketDisposition.NoProjection, disposition); } [Fact] public void AcceptedValidParent_WithdrawsWorldProjectionBeforePosePrerequisitesExist() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { fixture.Spawn(0x70000250u, generation: 1); LiveEntityRecord child = fixture.Spawn(0x70000251u, generation: 1); fixture.Controller.OnParentEvent(new ParentEvent.Parsed( ParentGuid: 0x70000250u, ChildGuid: 0x70000251u, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 2)); Assert.False(child.IsSpatiallyProjected); Assert.True(fixture.Live.ParentAttachments.TryGetProjection( 0x70000251u, out _)); } } [Fact] public void InvalidParent_ConsumesPositionTimestampButPreservesWorldProjection() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { fixture.Spawn(0x70000252u, generation: 1); LiveEntityRecord child = fixture.Spawn(0x70000253u, generation: 1); fixture.Controller.OnParentEvent(new ParentEvent.Parsed( ParentGuid: 0x70000252u, ChildGuid: 0x70000253u, ParentLocation: 1, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 2)); Assert.True(child.IsSpatiallyProjected); Assert.NotEqual(0u, child.FullCellId); Assert.True(fixture.Live.TryGetSnapshot( child.ServerGuid, out WorldSession.EntitySpawn snapshot)); Assert.NotNull(snapshot.Position); Assert.Null(snapshot.ParentGuid); Assert.Equal((ushort)2, snapshot.PositionSequence); Assert.False(fixture.Live.ParentAttachments.TryGetProjection( child.ServerGuid, out _)); } } [Fact] public void QueuedValidThenInvalidParent_LeavesValidParentCommitted() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord validParent = fixture.Spawn(0x70000259u, generation: 1); LiveEntityRecord invalidParent = fixture.Spawn(0x7000025Au, generation: 1); const uint childGuid = 0x7000025Bu; fixture.Controller.OnParentEvent(new ParentEvent.Parsed( validParent.ServerGuid, childGuid, 0, 0, 1, 1)); fixture.Controller.OnParentEvent(new ParentEvent.Parsed( invalidParent.ServerGuid, childGuid, 1, 0, 1, 2)); RuntimeEntityRecord childIdentity = fixture.RegisterOnly( childGuid, generation: 1, hasPosition: true); LiveEntityRecord child = fixture.Materialize(childIdentity); Assert.True(fixture.Live.TryGetSnapshot( childGuid, out WorldSession.EntitySpawn childSpawn)); fixture.Controller.OnSpawn(childSpawn); Assert.True(fixture.Live.TryGetSnapshot(childGuid, out childSpawn)); Assert.Equal(validParent.ServerGuid, childSpawn.ParentGuid); Assert.Null(childSpawn.Position); Assert.Equal((ushort)2, childSpawn.PositionSequence); var valid = new ParentAttachmentRelation( validParent.ServerGuid, childGuid, 0, 0, 1, 1); Assert.True(fixture.Live.ParentAttachments.IsCommitted(valid)); Assert.False(fixture.Live.ParentAttachments.TryGetStagedProjection( childGuid, out _)); } } [Fact] public void NoPositionCreateParent_CommitsAfterParentPartArrayValidation() { using var fixture = new ControllerFixture((_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(0x70000254u, generation: 1); RuntimeEntityRecord child = fixture.RegisterOnly( 0x70000255u, generation: 1, hasPosition: false); var update = new CreateParentUpdate( child.ServerGuid, parent.ServerGuid, ParentLocation: 0, PlacementId: 0, ChildInstanceSequence: 1, ChildPositionSequence: 1); fixture.CompleteFirstEntry(); Assert.True(fixture.Live.TryApplyCreateParent(update, out _)); fixture.Controller.OnCreateParentAccepted(update); Assert.True(fixture.Live.TryGetSnapshot( child.ServerGuid, out WorldSession.EntitySpawn snapshot)); Assert.Equal(parent.ServerGuid, snapshot.ParentGuid); Assert.Null(snapshot.Position); Assert.False(fixture.Live.TryGetRecord(child.ServerGuid, out _)); // #319: the parent was spawned with generation 1 - the committed // relation must carry the PARENT's live incarnation (late-bound at // accept time), not a hardcoded 0. This assertion previously pinned // the exact #319 defect (it passed for a nonzero-incarnation parent // only because AcceptCreateObjectRelation ignored the parent // entirely and always staged 0). var relation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); Assert.True(fixture.Live.ParentAttachments.IsCommitted(relation)); fixture.Poses.Publish( parent.WorldEntity!, Array.Empty()); fixture.Controller.OnPosePublished(parent.ServerGuid); Assert.True(fixture.Live.TryGetRecord( child.ServerGuid, out LiveEntityRecord childProjection)); Assert.NotNull(childProjection.WorldEntity); Assert.True(childProjection.IsSpatiallyProjected); Assert.Equal( LiveEntityProjectionKind.Attached, childProjection.ProjectionKind); } [Fact] public void SpawnParentWithoutAnimationFrame_UsesRetailPlacementZero() { using var fixture = new ControllerFixture((_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(0x70000270u, generation: 1); WorldSession.EntitySpawn childSpawn = ControllerFixture.SpawnData( 0x70000271u, generation: 1); childSpawn = childSpawn with { Position = null, ParentGuid = parent.ServerGuid, ParentLocation = 0, PlacementId = null, PositionSequence = 0, Physics = childSpawn.Physics!.Value with { Position = null, Parent = new PhysicsAttachment(parent.ServerGuid, 0u), AnimationFrame = null, }, }; fixture.Live.RegisterLiveEntity(childSpawn); fixture.Controller.OnSpawn(childSpawn); // #319: the parent was spawned with generation 1 - see the sibling // fix note in NoPositionCreateParent_CommitsAfterParentPartArrayValidation // above. var expected = new ParentAttachmentRelation( parent.ServerGuid, childSpawn.Guid, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 0); Assert.True(fixture.Live.ParentAttachments.IsCommitted(expected)); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); fixture.Controller.OnPosePublished(parent.ServerGuid); Assert.True(fixture.Live.TryGetRecord( childSpawn.Guid, out LiveEntityRecord child)); Assert.Equal(LiveEntityProjectionKind.Attached, child.ProjectionKind); Assert.NotNull(child.WorldEntity); } /// /// #319 F1, the key itself. Dual-parent-class matrix: a player-range /// parent (nonzero incarnation) and a creature-range parent (sequence /// 0). Drives the REAL producer () /// rather than the test helper CommitRenderedRelation most other /// tests in this file use (which hand-crafts the relation and never /// exercises the hardcoded-0 bug). Verifies the relation commits under /// the PARENT's exact live incarnation (not 0) and that both D1 (attach /// re-cell) and D2 (crossing propagation) reach the child. /// [Theory] [InlineData(0x50000777u, (ushort)9)] [InlineData(0x70000280u, (ushort)0)] public void OnSpawn_CreateObjectRelation_LateBindsToParentLiveIncarnationAndPropagatesCell( uint parentGuid, ushort parentIncarnation) { using var fixture = new ControllerFixture((_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(parentGuid, generation: parentIncarnation); Assert.NotEqual(0u, parent.Canonical.FullCellId); WorldSession.EntitySpawn childSpawn = ControllerFixture.SpawnData( 0x7000028Fu, generation: 1); childSpawn = childSpawn with { Position = null, ParentGuid = parent.ServerGuid, ParentLocation = 0, PlacementId = null, PositionSequence = 0, Physics = childSpawn.Physics!.Value with { Position = null, Parent = new PhysicsAttachment(parent.ServerGuid, 0u), AnimationFrame = null, }, }; fixture.Live.RegisterLiveEntity(childSpawn); // SABOTAGE PROBE: restoring the literal `ParentInstanceSequence: 0` // at EquippedChildRenderController.OnSpawn makes the player row // (parentIncarnation=9) fail this exact assertion while the // creature row (parentIncarnation=0) keeps passing - the precise // #319 signature. fixture.Controller.OnSpawn(childSpawn); Assert.True(fixture.Live.ParentAttachments.TryGetCommittedParent( childSpawn.Guid, out uint committedParentGuid, out ushort committedInstance)); Assert.Equal(parent.ServerGuid, committedParentGuid); Assert.Equal(parentIncarnation, committedInstance); // D1: attach re-cells the child to the parent's exact cell, inside // the same synchronous OnSpawn call (CommitAcceptedParentCellless // runs regardless of whether the render projection itself is ready // to materialize). Assert.True(fixture.Live.TryGetCanonical( childSpawn.Guid, out RuntimeEntityRecord childCanonical)); Assert.Equal(parent.Canonical.FullCellId, childCanonical.FullCellId); // D2: a later parent canonical-cell write propagates to the // committed child. const uint newCell = 0x01020001u; const uint newLandblock = 0x0102FFFFu; fixture.Spatial.AddLandblock(new LoadedLandblock( newLandblock, new LandBlock(), Array.Empty())); Assert.True(fixture.EntityObjects.CommitRebucket( parent.Canonical, newCell, newLandblock)); Assert.Equal(newCell, childCanonical.FullCellId); } /// /// #319 A1 (architecture review, 2026-08-05), the ordering fix's own /// end-to-end proof. Dual-parent-class matrix. A relation whose named /// incarnation mismatches the parent's live value must be refused /// BEFORE the canonical commit runs, not after - the original shape /// let CommitStagedParent (the canonical half: nulls the /// child's snapshot Position, writes ParentGuid, advances POSITION_TS) /// run first, then threw from inside CommitProjection, leaving /// the child canonically parented with no committed relation. Stages /// the mismatch directly (the real producer cannot construct one - it /// always resolves the parent's TRUE live incarnation) to isolate the /// ordering guarantee itself. /// [Theory] [InlineData(0x50000821u, (ushort)6)] [InlineData(0x70000299u, (ushort)0)] public void PrepareAndTryRealize_MismatchedIncarnation_RefusesBeforeCanonicalCommit( uint parentGuid, ushort parentIncarnation) { using var fixture = new ControllerFixture((_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); LiveEntityRecord parent = fixture.Spawn(parentGuid, generation: parentIncarnation); const uint childGuid = 0x700002A0u; fixture.RegisterOnly(childGuid, generation: 1, hasPosition: true); // #319 B1 (architecture review round 2, 2026-08-05): ChildPositionSequence // MUST equal the fixture's actual gate value (RegisterOnly/SpawnData // leaves Timestamps.Position and the top-level PositionSequence at 0, // and unlike NoPositionCreateParent_CommitsAfterParentPartArrayValidation // this test never calls TryApplyCreateParent/TryApplyParent to advance // it). A mismatched value here would make TryCommitParent's OWN // POSITION_TS gate (InboundPhysicsStateController.cs:299-312) refuse // the canonical commit regardless of ordering, so the three snapshot // assertions below would pass vacuously under a REVERTED ordering too // - the test would "fail under sabotage" only on the unrelated staged- // projection assertion, proving nothing about A1's tearing fix. var wrongRelation = new ParentAttachmentRelation( parent.ServerGuid, childGuid, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: (ushort)(parentIncarnation + 1), ChildPositionSequence: 0); fixture.Live.ParentAttachments.AcceptCreateObjectRelation(wrongRelation); fixture.Controller.OnWorldEntityRegistered(parent.ServerGuid); // The canonical layer must NEVER observe this child as parented - // if the ordering regressed, snapshot.Position would be null and // ParentGuid would be set even though no relation ever committed. Assert.True(fixture.Live.TryGetSnapshot( childGuid, out WorldSession.EntitySpawn snapshot)); Assert.Null(snapshot.ParentGuid); Assert.NotNull(snapshot.Position); Assert.False(fixture.Live.ParentAttachments.TryGetCommittedParent( childGuid, out _, out _)); // The refused relation does not linger and block Resolve forever - // it is rejected, not stranded. Assert.False(fixture.Live.ParentAttachments.TryGetStagedProjection( childGuid, out _)); } /// /// #319 A6 (architecture review, 2026-08-05). A prior revision of this /// fix deferred a CreateObject-carried relation whose parent was not /// yet addressable through a queue. Both reviews independently proved /// that shape is structurally unreachable in production for BOTH /// producers (raw CreateObject via OnSpawn AND the same- /// generation envelope via /// OnCreateParentAccepted): /// RuntimeEntityObjectLifetime.RegisterEntityCore's /// EnqueueDeferredCreate gate defers the ENTIRE CreateObject /// whenever its parent is not yet active, and does so BEFORE either /// wire shape is ever produced - the earlier version of this test /// only reached the deferred branch by calling /// OnCreateParentAccepted directly, bypassing that upstream /// gate. The deferral queue carried three independent defects (a /// missing child POSITION_TS gate, a placeholder-incarnation collision /// with the generation filters, unbounded accumulation) while never /// being exercised by production routing, so it was removed rather /// than fixed in place. This test now pins the CORRECTED behavior: an /// unaddressable parent at accept time is refused outright (logged, /// no state mutation, no crash) - dual-parent-class matrix. /// [Theory] [InlineData(0x50000811u, (ushort)4)] [InlineData(0x70000291u, (ushort)0)] public void OnCreateParentAccepted_ParentNotYetKnown_RefusesWithoutStateOrCrash( uint parentGuid, ushort parentIncarnation) { using var fixture = new ControllerFixture((_, _, _) => new ExactProjectionWithdrawalOutcome( ExactProjectionWithdrawalDisposition.Completed, Failure: null)); const uint childGuid = 0x7000029Fu; fixture.RegisterOnly(childGuid, generation: 1, hasPosition: false); fixture.CompleteFirstEntry(); var update = new CreateParentUpdate( childGuid, parentGuid, ParentLocation: 0, PlacementId: 0, ChildInstanceSequence: 1, ChildPositionSequence: 1); // The parent has not spawned yet - the relation must not be staged, // queued, or committed under any key, and the call must not throw. Assert.True(fixture.Live.TryApplyCreateParent(update, out _)); fixture.Controller.OnCreateParentAccepted(update); Assert.False(fixture.Live.ParentAttachments.TryGetStagedProjection( childGuid, out _)); Assert.False(fixture.Live.ParentAttachments.TryGetCommittedParent( childGuid, out _, out _)); Assert.Equal(0, fixture.Live.ParentAttachments.UnresolvedRelationCount); // The parent later becoming addressable does NOT retroactively // attach the refused relation - there is nothing left to retry // (the earlier deferred-queue design would have found and // committed it here; the corrected design does not). LiveEntityRecord parent = fixture.Spawn(parentGuid, generation: parentIncarnation); fixture.Controller.OnWorldEntityRegistered(parent.ServerGuid); Assert.False(fixture.Live.ParentAttachments.TryGetCommittedParent( childGuid, out _, out _)); } [Fact] public void ObjDesc_ReprojectsAttachedChildWithoutWorldPositionOrIdentityChange() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x70000272u, generation: 1); WorldSession.EntitySpawn childSpawn = ControllerFixture.SpawnData( 0x70000273u, generation: 1); childSpawn = childSpawn with { Position = null, ParentGuid = parent.ServerGuid, ParentLocation = 0, PlacementId = 0, PositionSequence = 0, Physics = childSpawn.Physics!.Value with { Position = null, Parent = new PhysicsAttachment(parent.ServerGuid, 0u), AnimationFrame = 0, }, }; fixture.Live.RegisterLiveEntity(childSpawn); fixture.Controller.OnSpawn(childSpawn); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); fixture.Controller.OnPosePublished(parent.ServerGuid); Assert.True(fixture.Live.TryGetRecord( childSpawn.Guid, out LiveEntityRecord child)); WorldEntity entity = child.WorldEntity!; Assert.Null(entity.PaletteOverride); WorldSession.EntitySpawn grandchildSpawn = ControllerFixture.SpawnData( 0x70000274u, generation: 1); grandchildSpawn = grandchildSpawn with { Position = null, ParentGuid = child.ServerGuid, ParentLocation = 0, PlacementId = 0, PositionSequence = 0, Physics = grandchildSpawn.Physics!.Value with { Position = null, Parent = new PhysicsAttachment(child.ServerGuid, 0u), AnimationFrame = 0, }, }; fixture.Live.RegisterLiveEntity(grandchildSpawn); fixture.Controller.OnSpawn(grandchildSpawn); Assert.True(fixture.Live.TryGetRecord( grandchildSpawn.Guid, out LiveEntityRecord grandchild)); WorldEntity grandchildEntity = grandchild.WorldEntity!; Assert.Equal( LiveEntityProjectionKind.Attached, grandchild.ProjectionKind); Assert.True(fixture.Live.TryApplyObjDesc( new ObjDescEvent.Parsed( child.ServerGuid, new CreateObject.ModelData( BasePaletteId: 0x04000022u, [new CreateObject.SubPaletteSwap( 0x0F000033u, Offset: 4, Length: 8)], Array.Empty(), Array.Empty()), InstanceSequence: 1, ObjDescSequence: 1), out _)); Assert.Null(child.Snapshot.Position); Assert.True(fixture.Controller.TryApplyAttachedAppearance( child, child.ObjDescAuthorityVersion)); Assert.Same(entity, child.WorldEntity); Assert.True(child.IsSpatiallyProjected); Assert.Equal(LiveEntityProjectionKind.Attached, child.ProjectionKind); Assert.Equal((uint)0x04000022u, child.Snapshot.BasePaletteId); Assert.NotNull(entity.PaletteOverride); Assert.Equal(0x04000022u, entity.PaletteOverride!.BasePaletteId); PaletteOverride.SubPaletteRange range = Assert.Single(entity.PaletteOverride.SubPalettes); Assert.Equal(0x0F000033u, range.SubPaletteId); Assert.Equal((byte)4, range.Offset); Assert.Equal((byte)8, range.Length); Assert.Same(grandchildEntity, grandchild.WorldEntity); Assert.True(grandchild.IsSpatiallyProjected); Assert.Equal( LiveEntityProjectionKind.Attached, grandchild.ProjectionKind); } } [Fact] public void ChildWithoutSetup_StillCommitsLogicalParenting() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x7000025Cu, generation: 1); RuntimeEntityRecord childIdentity = fixture.RegisterOnly( 0x7000025Du, generation: 1, hasPosition: true, hasSetup: false); LiveEntityRecord child = fixture.Materialize(childIdentity); child.HasPartArray = false; var update = new ParentEvent.Parsed( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); fixture.Controller.OnParentEvent(update); Assert.True(fixture.Live.TryGetSnapshot( child.ServerGuid, out WorldSession.EntitySpawn snapshot)); Assert.Equal(parent.ServerGuid, snapshot.ParentGuid); Assert.Null(snapshot.Position); Assert.False(child.IsSpatiallyProjected); Assert.True(fixture.Live.ParentAttachments.IsCommitted(new( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1))); } } [Fact] public void EmbeddedSameGenerationCreateParent_UsesStagedRoute() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x7000025Eu, generation: 1); LiveEntityRecord child = fixture.Spawn(0x7000025Fu, generation: 1); PhysicsTimestamps timestamps = new( Position: 1, Movement: 0, State: 0, Vector: 0, Teleport: 0, ServerControlledMove: 0, ForcePosition: 0, ObjDesc: 0, Instance: 1); PhysicsSpawnData physics = new( RawState: 0x408u, Position: null, Movement: null, AnimationFrame: 0, SetupTableId: 0x02000001u, MotionTableId: null, SoundTableId: null, PhysicsScriptTableId: null, Parent: new PhysicsAttachment(parent.ServerGuid, 0), Children: null, Scale: null, Friction: null, Elasticity: null, Translucency: null, Velocity: null, Acceleration: null, AngularVelocity: null, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); WorldSession.EntitySpawn incoming = ControllerFixture.SpawnData( child.ServerGuid, generation: 1) with { Position = null, PositionSequence = 1, PlacementId = 0, ParentGuid = parent.ServerGuid, ParentLocation = 0, Physics = physics, }; LiveEntityRegistrationResult refresh = fixture.Live.RegisterLiveEntity(incoming); CreateParentUpdate update = Assert.IsType( refresh.Inbound.SameGenerationEvents!.Value.Parent); Assert.True(fixture.Live.TryApplyCreateParent(update, out _)); fixture.Controller.OnCreateParentAccepted(update); Assert.True(fixture.Live.TryGetSnapshot( child.ServerGuid, out WorldSession.EntitySpawn snapshot)); Assert.Equal(parent.ServerGuid, snapshot.ParentGuid); Assert.Null(snapshot.Position); } } [Fact] public void NewWaitingParent_DoesNotDisplaceCommittedRecovery() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord oldParent = fixture.Spawn(0x70000256u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000257u, generation: 1, LiveEntityProjectionKind.Attached); RuntimeEntityRecord waitingParent = fixture.RegisterOnly( 0x70000258u, generation: 1, hasPosition: true); var oldRelation = new ParentAttachmentRelation( oldParent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); fixture.Live.ParentAttachments.AcceptCreateObjectRelation(oldRelation); Assert.True(fixture.Live.TryApplyParent(new ParentEvent.Parsed( oldRelation.ParentGuid, oldRelation.ChildGuid, oldRelation.ParentLocation, oldRelation.PlacementId, oldRelation.ParentInstanceSequence, oldRelation.ChildPositionSequence), out _)); Assert.True(fixture.Live.CommitStagedParent(oldRelation, out _)); Assert.True(fixture.Live.ParentAttachments.CommitProjection(oldRelation)); fixture.Live.ParentAttachments.MarkProjected( oldRelation, ParentProjectionCandidateKind.Recovery); fixture.InstallAttached(oldParent, child); var newer = new ParentEvent.Parsed( waitingParent.ServerGuid, child.ServerGuid, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 2); fixture.Controller.OnParentEvent(newer); Assert.True(fixture.Live.ParentAttachments.TryGetStagedProjection( child.ServerGuid, out ParentAttachmentRelation staged)); Assert.Equal(waitingParent.ServerGuid, staged.ParentGuid); fixture.Controller.Tick(); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.True(fixture.Live.ParentAttachments.TryGetStagedProjection( child.ServerGuid, out staged)); Assert.Equal(waitingParent.ServerGuid, staged.ParentGuid); Assert.True(fixture.Live.ParentAttachments.TryGetRecoveryProjection( child.ServerGuid, out ParentAttachmentRelation recovery)); Assert.Equal(oldParent.ServerGuid, recovery.ParentGuid); fixture.Materialize(waitingParent); fixture.Controller.OnWorldEntityRegistered(waitingParent.ServerGuid); Assert.True(fixture.Live.ParentAttachments.IsCommitted(staged)); Assert.True(fixture.Live.ParentAttachments.TryGetRecoveryProjection( child.ServerGuid, out ParentAttachmentRelation committedRecovery)); Assert.Equal(staged, committedRecovery); Assert.True(fixture.Live.TryGetSnapshot( child.ServerGuid, out WorldSession.EntitySpawn snapshot)); Assert.Equal(waitingParent.ServerGuid, snapshot.ParentGuid); } } [Fact] public void OrdinaryRemoval_PendingFailureRetriesExactCaptureOnTick() { int attempts = 0; ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { attempts++; if (attempts == 1) { return new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("ordinary cleanup failed")); } bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x70000260u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000261u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.Live.ParentAttachments.AcceptCreateObjectRelation(new( parent.ServerGuid, child.ServerGuid, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 1)); TargetInvocationException error = Assert.Throws(() => fixture.InvokeOrdinaryRemoval(0x70000261u)); Assert.IsType(error.InnerException); fixture.Controller.Tick(); Assert.Equal(2, attempts); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.False(child.IsSpatiallyProjected); Assert.False(fixture.Live.ParentAttachments.TryGetProjection( child.ServerGuid, out _)); } } [Fact] public void DetachedRemoval_PendingFailureRetriesAndPreservesRollbackHistory() { int attempts = 0; ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { attempts++; if (attempts == 1) { return new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("detached cleanup failed")); } bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x70000262u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000263u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); var relation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, ParentLocation: 0, PlacementId: 0, ParentInstanceSequence: 1, ChildPositionSequence: 1); fixture.Live.ParentAttachments.AcceptCreateObjectRelation(relation); Assert.True(fixture.Live.ParentAttachments.CommitProjection(relation)); fixture.Live.ParentAttachments.MarkProjected( relation, ParentProjectionCandidateKind.Recovery); TargetInvocationException error = Assert.Throws(() => fixture.InvokeDetachedRemoval(child.ServerGuid)); Assert.IsType(error.InnerException); fixture.Controller.Tick(); Assert.Equal(2, attempts); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.False(child.IsSpatiallyProjected); Assert.True(fixture.Live.ParentAttachments.RestoreLastAccepted( child.ServerGuid)); Assert.True(fixture.Live.ParentAttachments.TryGetRecoveryProjection( child.ServerGuid, out ParentAttachmentRelation restored)); Assert.Equal(relation, restored); } } [Fact] public void Unparent_WithdrawsCompleteAttachedSubtreeAndRecoversDescendants() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord parent = fixture.Spawn(0x70000264u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000265u, generation: 1, LiveEntityProjectionKind.Attached); LiveEntityRecord grandchild = fixture.Spawn( 0x70000266u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.InstallAttached(child, grandchild); var childRelation = new ParentAttachmentRelation( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1); var grandchildRelation = new ParentAttachmentRelation( child.ServerGuid, grandchild.ServerGuid, 0, 0, 1, 1); fixture.CommitRenderedRelation(childRelation); fixture.CommitRenderedRelation(grandchildRelation); ChildUnparentDisposition result = fixture.Controller.OnChildBecameUnparented(child.ServerGuid); Assert.Equal(ChildUnparentDisposition.Completed, result); Assert.Empty(fixture.Controller.AttachedEntityIds); Assert.False(child.IsSpatiallyProjected); Assert.False(grandchild.IsSpatiallyProjected); Assert.False(fixture.Live.ParentAttachments.RestoreLastAccepted( child.ServerGuid)); Assert.True(fixture.Live.ParentAttachments.TryGetRecoveryProjection( grandchild.ServerGuid, out ParentAttachmentRelation recovered)); Assert.Equal(grandchildRelation, recovered); } } [Fact] public void PendingDetachedRemoval_RollbackCancelsRetryAndKeepsProjection() { int attempts = 0; using var fixture = new ControllerFixture((_, _, _) => { attempts++; return new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("component withdrawal failed")); }); LiveEntityRecord parent = fixture.Spawn(0x70000267u, generation: 1); LiveEntityRecord child = fixture.Spawn( 0x70000268u, generation: 1, LiveEntityProjectionKind.Attached); fixture.InstallAttached(parent, child); fixture.CommitRenderedRelation(new( parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1)); fixture.Poses.Publish(parent.WorldEntity!, Array.Empty()); fixture.Objects.AddOrUpdate(new ClientObject { ObjectId = child.ServerGuid, WielderId = parent.ServerGuid, CurrentlyEquippedLocation = EquipMask.MeleeWeapon, }); Assert.Throws(() => fixture.Objects.MoveItemOptimistic( child.ServerGuid, newContainerId: 0x50000001u, newSlot: 0)); Assert.True(fixture.Objects.RollbackMove(child.ServerGuid)); fixture.Controller.Tick(); Assert.Equal(1, attempts); Assert.True(child.IsSpatiallyProjected); Assert.Single(fixture.Controller.AttachedEntityIds); } [Fact] public void InventoryPutObjectIn3D_DoesNotWithdrawRecoveredWorldProjection() { int withdrawals = 0; ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { withdrawals++; bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { const uint guid = 0x7000026Au; const uint player = 0x50000001u; LiveEntityRecord dropped = fixture.Spawn(guid, generation: 1); fixture.Objects.AddOrUpdate(new ClientObject { ObjectId = guid, ContainerId = player, CurrentlyEquippedLocation = EquipMask.None, }); Assert.True(fixture.Objects.ApplyConfirmedServerMove( guid, newContainerId: 0u, newWielderId: 0u)); Assert.Equal(0, withdrawals); Assert.True(dropped.IsSpatiallyProjected); Assert.Equal(LiveEntityProjectionKind.World, dropped.ProjectionKind); } } [Fact] public void OrphanRollback_PendingFailureRetainsExactRetryOwner() { int attempts = 0; ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { attempts++; if (attempts == 1) { return new( ExactProjectionWithdrawalDisposition.Pending, new InvalidOperationException("orphan component cleanup failed")); } bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord orphan = fixture.Spawn(0x70000269u, generation: 1); TargetInvocationException error = Assert.Throws(() => fixture.InvokeOrphanRemoval(orphan)); Assert.IsType(error.InnerException); fixture.Controller.Tick(); Assert.Equal(2, attempts); Assert.False(orphan.IsSpatiallyProjected); } } [Fact] public void RecoveryContinuation_PostCommitFailureIsNotReplayed() { ControllerFixture? fixture = null; fixture = new ControllerFixture((record, positionVersion, projectionVersion) => { bool completed = fixture!.Live.WithdrawLiveEntityProjection( record, positionVersion, projectionVersion); return new( completed ? ExactProjectionWithdrawalDisposition.Completed : ExactProjectionWithdrawalDisposition.Superseded, Failure: null); }); using (fixture) { LiveEntityRecord record = fixture.Spawn(0x70000270u, generation: 1); int continuations = 0; Assert.Throws(() => fixture.Controller.OnChildBecameUnparented( 0x70000270u, () => { continuations++; Assert.True(fixture.Live.RebucketLiveEntity( 0x70000270u, 0x01010001u)); throw new InvalidOperationException("observer failed after recovery"); })); fixture.Controller.Tick(); Assert.Equal(1, continuations); Assert.True(record.IsSpatiallyProjected); } } private static LiveEntityRecord ChildRecord() => LiveEntityTestFixture.CreateExactProjectionRecord( new WorldSession.EntitySpawn( 0x70000100u, new CreateObject.ServerPosition( 0x01010001u, 0f, 0f, 0f, 1f, 0f, 0f, 0f), 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), BasePaletteId: null, ObjScale: null, Name: "attached fixture", ItemType: null, MotionState: null, MotionTableId: null, InstanceSequence: 1)); private sealed class ControllerFixture : IDisposable { private const uint Cell = 0x01010001u; private readonly DeferredLiveEntityRuntimeComponentLifecycle _lifecycle = new(); private readonly Setup _setup; private readonly AcDream.Runtime.Session.RuntimeFirstEntryDriveController _firstEntry; private sealed class NullCollisionSource : 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() { } } internal ControllerFixture( Func withdraw) { Spatial.AddLandblock(new LoadedLandblock( (Cell & 0xFFFF0000u) | 0xFFFFu, new LandBlock(), Array.Empty())); EntityObjects = new RuntimeEntityObjectLifetime(); EntityObjects.BindEventContext( static () => new AcDream.Runtime.RuntimeGenerationToken(1UL), static () => 1UL); _firstEntry = new AcDream.Runtime.Session.RuntimeFirstEntryDriveController( EntityObjects, new AcDream.Runtime.GameRuntimeClock(), new NullCollisionSource(), () => AcDream.Runtime.Gameplay.PlayerMovementConstructionOptions.Fallback, static _ => new AcDream.Runtime.Gameplay.RuntimeLocalPlayerPhysicsActivationPreparation( 0.48f, 1.835f, AcDream.Runtime.Gameplay.RuntimeLocalPlayerShadowDisposition.ProvenShapeless)); Live = new LiveEntityRuntime( Spatial, new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), _lifecycle, EntityObjects); _setup = new Setup { HoldingLocations = { [(ParentLocation)0] = new LocationType { PartId = -1, Frame = new Frame { Orientation = Quaternion.Identity }, }, }, }; IDatReaderWriter dat = DispatchProxy.Create(); ((NullDatProxy)(object)dat).Setup = _setup; Controller = new EquippedChildRenderController( dat, new object(), Objects, Live, Poses, update => Live.TryApplyParent(update, out _), withdraw); _lifecycle.Bind(new DelegateLiveEntityRuntimeComponentLifecycle( Controller.OnLogicalTeardown)); } internal GpuWorldState Spatial { get; } = new(); internal RuntimeEntityObjectLifetime EntityObjects { get; } /// /// C3c: completes fresh residences through the first-entry /// conductors (a celless route performs no SetPosition, so its drain /// completes synchronously), releasing the lease so legacy direct /// CreateParent/Parent application stays valid post-flip. The /// controller is constructed lazily but binds no notification of its /// own tracking value until first use, so it sweeps active leases /// directly instead. /// internal void CompleteFirstEntry() { _firstEntry.DriveAll(); } internal ClientObjectTable Objects { get; } = new(); internal EntityEffectPoseRegistry Poses { get; } = new(); internal LiveEntityRuntime Live { get; } internal EquippedChildRenderController Controller { get; } internal LiveEntityRecord Spawn( uint guid, ushort generation, LiveEntityProjectionKind kind = LiveEntityProjectionKind.World) { RuntimeEntityRecord canonical = RegisterOnly(guid, generation, hasPosition: true); return Materialize(canonical, kind); } internal RuntimeEntityRecord RegisterOnly( uint guid, ushort generation, bool hasPosition, bool hasSetup = true) { WorldSession.EntitySpawn spawn = SpawnData(guid, generation); if (!hasPosition) { spawn = spawn with { Position = null, Physics = spawn.Physics!.Value with { Position = null }, }; } if (!hasSetup) spawn = spawn with { SetupTableId = null }; return Assert.IsType( Live.RegisterLiveEntity(spawn).Canonical); } internal LiveEntityRecord Materialize( RuntimeEntityRecord canonical, LiveEntityProjectionKind kind = LiveEntityProjectionKind.World) { WorldEntity? entity = Live.MaterializeLiveEntity( canonical, Cell, id => new WorldEntity { Id = id, ServerGuid = canonical.ServerGuid, SourceGfxObjOrSetupId = 0x02000001u, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = Array.Empty(), ParentCellId = Cell, }, kind, initializeProjection: null, out LiveEntityRecord? projected); Assert.NotNull(entity); LiveEntityRecord record = Assert.IsType(projected); record.HasPartArray = true; return record; } internal static WorldSession.EntitySpawn SpawnData(uint guid, ushort generation) { // C3c: residence admission freezes the raw create and requires // the flattened parser projections to agree with the nested // PhysicsDesc block (HasConsistentCreateIdentityAndParent). var position = new CreateObject.ServerPosition( Cell, 0f, 0f, 0f, 1f, 0f, 0f, 0f); var physics = new PhysicsSpawnData( RawState: 0u, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: null, SoundTableId: null, PhysicsScriptTableId: null, Parent: null, Children: null, Scale: null, Friction: null, Elasticity: null, Translucency: null, Velocity: null, Acceleration: null, AngularVelocity: null, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: new PhysicsTimestamps( Position: 0, Movement: 0, State: 0, Vector: 0, Teleport: 0, ServerControlledMove: 0, ForcePosition: 0, ObjDesc: 0, Instance: generation)); return new WorldSession.EntitySpawn( guid, position, 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), BasePaletteId: null, ObjScale: null, Name: "attached fixture", ItemType: null, MotionState: null, MotionTableId: null, InstanceSequence: generation, Physics: physics); } internal void InstallAttached( LiveEntityRecord parent, LiveEntityRecord child) { Type attachedType = typeof(EquippedChildRenderController).GetNestedType( "AttachedChild", BindingFlags.NonPublic)!; object attached = Activator.CreateInstance( attachedType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, args: [ parent, child, parent.ServerGuid, child.ServerGuid, (ParentLocation)0, (Placement)0, _setup, _setup, Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), 1f, child.WorldEntity!, ], culture: null)!; FieldInfo mapField = typeof(EquippedChildRenderController).GetField( "_attachedByChild", BindingFlags.Instance | BindingFlags.NonPublic)!; var map = (IDictionary)mapField.GetValue(Controller)!; map.Add(child.ProjectionKey!.Value, attached); } /// /// C4 route 7 D4 test support (N6, round-3 review): removes ONLY /// 's LiveEntityRuntime projection /// entry (via the same private _projections store /// MaterializeLiveEntity's own rollback paths use), leaving /// any separately-installed _attachedByChild entry (from /// ) untouched. Constructs the /// deliberately abnormal "stale AttachedChild, no live projection" /// state /// exists to catch — no production path is expected to reach it, so /// this reflection is the test's own construction, not a stand-in /// for a real caller. /// internal void RemoveProjectionOnly(LiveEntityRecord record) { FieldInfo projectionsField = typeof(LiveEntityRuntime).GetField( "_projections", BindingFlags.Instance | BindingFlags.NonPublic)!; object projections = projectionsField.GetValue(Live)!; MethodInfo removeActive = projections.GetType().GetMethod( "RemoveActive", BindingFlags.Instance | BindingFlags.Public)!; Assert.True((bool)removeActive.Invoke(projections, [record])!); } internal void CommitRenderedRelation(ParentAttachmentRelation relation) { Live.ParentAttachments.AcceptCreateObjectRelation(relation); Assert.True(Live.ParentAttachments.CommitProjection(relation)); Live.ParentAttachments.MarkProjected( relation, ParentProjectionCandidateKind.Recovery); } internal void InvokeOrdinaryRemoval(uint guid) { MethodInfo method = typeof(EquippedChildRenderController).GetMethod( "TearDownCurrentObjectProjections", BindingFlags.Instance | BindingFlags.NonPublic)!; method.Invoke(Controller, [guid]); } internal void InvokeDetachedRemoval(uint guid) { MethodInfo method = typeof(EquippedChildRenderController).GetMethod( "BeginDetachedRemoval", BindingFlags.Instance | BindingFlags.NonPublic)!; method.Invoke(Controller, [guid]); } internal void InvokeOrphanRemoval(LiveEntityRecord record) { FieldInfo mapField = typeof(EquippedChildRenderController).GetField( "_pendingOrphanRemovalByChild", BindingFlags.Instance | BindingFlags.NonPublic)!; object map = mapField.GetValue(Controller)!; MethodInfo method = typeof(EquippedChildRenderController).GetMethod( "BeginProjectionSubtreeWithdrawal", BindingFlags.Instance | BindingFlags.NonPublic)!; method.Invoke(Controller, [map, record, false, false]); } public void Dispose() => Controller.Dispose(); } private class NullDatProxy : DispatchProxy { internal Setup? Setup { get; set; } protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) { if (targetMethod?.Name == "Get" && targetMethod.ReturnType == typeof(Setup)) { return Setup; } if (targetMethod?.ReturnType == typeof(void)) return null; if (targetMethod?.ReturnType.IsValueType == true) return Activator.CreateInstance(targetMethod.ReturnType); return null; } } }