refactor(world): extract live appearance and parenting

Move ObjDesc, Parent, Pickup, child-ready, and retained projection recovery into LiveEntityHydrationController while preserving retail timestamp and leave-world semantics. Pin ready publication to exact projection authority and restore attached descendant chains synchronously.
This commit is contained in:
Erik 2026-07-21 18:10:07 +02:00
parent 40352d5a7a
commit fe5514967c
9 changed files with 1098 additions and 199 deletions

View file

@ -433,6 +433,134 @@ public sealed class EquippedChildProjectionWithdrawalTests
Assert.Equal(LiveEntityProjectionKind.Attached, child.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) with
{
Position = null,
ParentGuid = parent.ServerGuid,
ParentLocation = 0,
PlacementId = null,
PositionSequence = 0,
};
LiveEntityRecord child = fixture.Live.RegisterLiveEntity(childSpawn).Record!;
fixture.Controller.OnSpawn(childSpawn);
var expected = new ParentAttachmentRelation(
parent.ServerGuid,
child.ServerGuid,
ParentLocation: 0,
PlacementId: 0,
ParentInstanceSequence: 0,
ChildPositionSequence: 0);
Assert.True(fixture.Live.ParentAttachments.IsCommitted(expected));
fixture.Poses.Publish(parent.WorldEntity!, Array.Empty<Matrix4x4>());
fixture.Controller.OnPosePublished(parent.ServerGuid);
Assert.Equal(LiveEntityProjectionKind.Attached, child.ProjectionKind);
Assert.NotNull(child.WorldEntity);
}
[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) with
{
Position = null,
ParentGuid = parent.ServerGuid,
ParentLocation = 0,
PlacementId = 0,
PositionSequence = 0,
};
LiveEntityRecord child = fixture.Live.RegisterLiveEntity(childSpawn).Record!;
fixture.Controller.OnSpawn(childSpawn);
fixture.Poses.Publish(parent.WorldEntity!, Array.Empty<Matrix4x4>());
fixture.Controller.OnPosePublished(parent.ServerGuid);
WorldEntity entity = child.WorldEntity!;
Assert.Null(entity.PaletteOverride);
WorldSession.EntitySpawn grandchildSpawn = ControllerFixture.SpawnData(
0x70000274u,
generation: 1) with
{
Position = null,
ParentGuid = child.ServerGuid,
ParentLocation = 0,
PlacementId = 0,
PositionSequence = 0,
};
LiveEntityRecord grandchild = fixture.Live.RegisterLiveEntity(
grandchildSpawn).Record!;
fixture.Controller.OnSpawn(grandchildSpawn);
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<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
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()
{