feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
31a0889f08
commit
f961d70023
77 changed files with 12513 additions and 1871 deletions
|
|
@ -164,6 +164,113 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
Assert.Equal([fixture.Entity.Id], fixture.PartArrayEnterWorld);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StateAcceptedDuringHiddenEffect_DrainsAfterCompleteHiddenTail()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
bool nestedAccepted = false;
|
||||
fixture.OnTypedPlay = type =>
|
||||
{
|
||||
if (type != LiveEntityPresentationController.HiddenScriptType
|
||||
|| nestedAccepted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nestedAccepted = true;
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
Fixture.Guid,
|
||||
(uint)PhysicsStateFlags.ReportCollisions,
|
||||
1,
|
||||
3),
|
||||
out _));
|
||||
Assert.True(fixture.Controller.OnStateAccepted(Fixture.Guid));
|
||||
};
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
Fixture.Guid,
|
||||
(uint)(PhysicsStateFlags.Hidden
|
||||
| PhysicsStateFlags.ReportCollisions),
|
||||
1,
|
||||
2),
|
||||
out _));
|
||||
Assert.True(fixture.Controller.OnStateAccepted(Fixture.Guid));
|
||||
|
||||
Assert.True(nestedAccepted);
|
||||
Assert.Equal(
|
||||
[
|
||||
"effect:76", "children:True", "part-array", "target",
|
||||
"effect:75", "children:False", "part-array",
|
||||
],
|
||||
fixture.PresentationOrder);
|
||||
Assert.True(fixture.Entity.IsDrawVisible);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteAndGuidReuseDuringHiddenEffect_StopsOldTransitionTail()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
Assert.True(fixture.Runtime.TryGetRecord(
|
||||
Fixture.Guid,
|
||||
out LiveEntityRecord oldRecord));
|
||||
WorldEntity? replacement = null;
|
||||
fixture.OnTypedPlay = type =>
|
||||
{
|
||||
if (type != LiveEntityPresentationController.HiddenScriptType
|
||||
|| replacement is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixture.Controller.Forget(oldRecord);
|
||||
fixture.Shadows.Deregister(fixture.Entity.Id);
|
||||
Assert.True(fixture.Runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(Fixture.Guid, 1),
|
||||
isLocalPlayer: false));
|
||||
fixture.Runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions,
|
||||
instanceSequence: 2));
|
||||
replacement = fixture.Runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
0x01010001u,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(20f, 20f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
});
|
||||
Assert.NotNull(replacement);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
};
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
Fixture.Guid,
|
||||
(uint)(PhysicsStateFlags.Hidden
|
||||
| PhysicsStateFlags.ReportCollisions),
|
||||
1,
|
||||
2),
|
||||
out _));
|
||||
Assert.False(fixture.Controller.OnStateAccepted(Fixture.Guid));
|
||||
|
||||
Assert.NotNull(replacement);
|
||||
Assert.Equal(["effect:76"], fixture.PresentationOrder);
|
||||
Assert.Empty(fixture.ChildNoDraw);
|
||||
Assert.Empty(fixture.PartArrayEnterWorld);
|
||||
Assert.Empty(fixture.InvalidTargets);
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Fixture.Guid, out var current));
|
||||
Assert.Same(replacement, current.WorldEntity);
|
||||
Assert.NotSame(oldRecord, current);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnHideWhileLandblockPending_RestoresShadowWhenProjectionReturns()
|
||||
{
|
||||
|
|
@ -200,6 +307,285 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibleOrdinaryProjectionPending_SuspendsAndHydrationRestoresShadow()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
|
||||
Assert.True(fixture.Runtime.RebucketLiveEntity(
|
||||
Fixture.Guid,
|
||||
0x02020001u));
|
||||
|
||||
Assert.False(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
Assert.Equal(1, fixture.Shadows.RetainedRegistrationCount);
|
||||
Assert.Equal(1, fixture.Shadows.SuspendedRegistrationCount);
|
||||
Assert.True(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
|
||||
fixture.Spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0202FFFFu,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.Same(
|
||||
fixture.Entity,
|
||||
Assert.Single(fixture.Runtime.WorldEntities).Value);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
Assert.Equal(0, fixture.Shadows.SuspendedRegistrationCount);
|
||||
Assert.False(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PendingVisibilityCallback_GuidReuseCannotRestoreOldShadowOwner()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
Assert.True(fixture.Runtime.TryGetRecord(
|
||||
Fixture.Guid,
|
||||
out LiveEntityRecord oldRecord));
|
||||
WorldEntity oldEntity = fixture.Entity;
|
||||
WorldEntity? replacement = null;
|
||||
|
||||
fixture.Runtime.ProjectionVisibilityChanged += (record, visible) =>
|
||||
{
|
||||
if (visible
|
||||
|| replacement is not null
|
||||
|| !ReferenceEquals(record, oldRecord))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixture.Controller.Forget(oldRecord);
|
||||
fixture.Shadows.Deregister(oldEntity.Id);
|
||||
Assert.True(fixture.Runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(Fixture.Guid, 1),
|
||||
isLocalPlayer: false));
|
||||
fixture.Runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions,
|
||||
instanceSequence: 2));
|
||||
replacement = fixture.Runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
0x01010001u,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(20f, 20f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
});
|
||||
Assert.NotNull(replacement);
|
||||
fixture.Shadows.Register(
|
||||
replacement.Id,
|
||||
0x01000001u,
|
||||
replacement.Position,
|
||||
replacement.Rotation,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0x01010000u,
|
||||
state: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
seedCellId: 0x01010001u);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
};
|
||||
|
||||
Assert.False(fixture.Runtime.RebucketLiveEntity(
|
||||
Fixture.Guid,
|
||||
0x02020001u));
|
||||
|
||||
Assert.NotNull(replacement);
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Fixture.Guid, out var current));
|
||||
Assert.NotSame(oldRecord, current);
|
||||
Assert.Same(replacement, current.WorldEntity);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
Assert.Equal(1, fixture.Shadows.RetainedRegistrationCount);
|
||||
Assert.Equal(0, fixture.Shadows.SuspendedRegistrationCount);
|
||||
Assert.Contains(
|
||||
fixture.Shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == replacement.Id);
|
||||
Assert.DoesNotContain(
|
||||
fixture.Shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == oldEntity.Id);
|
||||
Assert.False(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitialPendingProjection_ReadyBarrierSuspendsAndHydrationRestoresShadow()
|
||||
{
|
||||
const uint pendingCell = 0x02020001u;
|
||||
var spatial = new GpuWorldState();
|
||||
var shadows = new ShadowObjectRegistry();
|
||||
var runtime = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions));
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(
|
||||
runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
pendingCell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(10f, 10f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = pendingCell,
|
||||
}));
|
||||
shadows.Register(
|
||||
entity.Id,
|
||||
0x01000001u,
|
||||
entity.Position,
|
||||
entity.Rotation,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0x02020000u,
|
||||
state: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
seedCellId: pendingCell);
|
||||
using var controller = new LiveEntityPresentationController(
|
||||
runtime,
|
||||
shadows,
|
||||
(_, _, _) => true,
|
||||
liveCenter: () => (2, 2));
|
||||
|
||||
Assert.True(controller.OnLiveEntityReady(Fixture.Guid));
|
||||
|
||||
Assert.Equal(0, shadows.TotalRegistered);
|
||||
Assert.Equal(1, shadows.RetainedRegistrationCount);
|
||||
Assert.Equal(1, shadows.SuspendedRegistrationCount);
|
||||
Assert.True(controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
|
||||
spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0202FFFFu,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Equal(1, shadows.TotalRegistered);
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
Assert.False(controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitialPendingHydrationCallback_GuidReuseCannotRestoreOldShadow()
|
||||
{
|
||||
const uint pendingCell = 0x02020001u;
|
||||
var spatial = new GpuWorldState();
|
||||
var shadows = new ShadowObjectRegistry();
|
||||
var runtime = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions));
|
||||
WorldEntity oldEntity = Assert.IsType<WorldEntity>(
|
||||
runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
pendingCell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(10f, 10f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = pendingCell,
|
||||
}));
|
||||
Assert.True(runtime.TryGetRecord(Fixture.Guid, out LiveEntityRecord oldRecord));
|
||||
shadows.Register(
|
||||
oldEntity.Id,
|
||||
0x01000001u,
|
||||
oldEntity.Position,
|
||||
oldEntity.Rotation,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0x02020000u,
|
||||
state: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
seedCellId: pendingCell);
|
||||
|
||||
LiveEntityPresentationController? controller = null;
|
||||
WorldEntity? replacement = null;
|
||||
runtime.ProjectionVisibilityChanged += (record, visible) =>
|
||||
{
|
||||
if (!visible
|
||||
|| replacement is not null
|
||||
|| !ReferenceEquals(record, oldRecord))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
controller!.Forget(oldRecord);
|
||||
shadows.Deregister(oldEntity.Id);
|
||||
Assert.True(runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(Fixture.Guid, 1),
|
||||
isLocalPlayer: false));
|
||||
runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions,
|
||||
instanceSequence: 2));
|
||||
replacement = runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
pendingCell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(20f, 20f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = pendingCell,
|
||||
});
|
||||
Assert.NotNull(replacement);
|
||||
shadows.Register(
|
||||
replacement.Id,
|
||||
0x01000001u,
|
||||
replacement.Position,
|
||||
replacement.Rotation,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0x02020000u,
|
||||
state: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
seedCellId: pendingCell);
|
||||
Assert.True(controller.OnLiveEntityReady(Fixture.Guid));
|
||||
};
|
||||
controller = new LiveEntityPresentationController(
|
||||
runtime,
|
||||
shadows,
|
||||
(_, _, _) => true,
|
||||
liveCenter: () => (2, 2));
|
||||
Assert.True(controller.OnLiveEntityReady(Fixture.Guid));
|
||||
Assert.Equal(1, shadows.SuspendedRegistrationCount);
|
||||
|
||||
spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0202FFFFu,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.NotNull(replacement);
|
||||
Assert.True(runtime.TryGetRecord(Fixture.Guid, out var current));
|
||||
Assert.NotSame(oldRecord, current);
|
||||
Assert.Same(replacement, current.WorldEntity);
|
||||
Assert.Equal(1, shadows.TotalRegistered);
|
||||
Assert.Equal(1, shadows.RetainedRegistrationCount);
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
Assert.Contains(
|
||||
shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == replacement.Id);
|
||||
Assert.DoesNotContain(
|
||||
shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == oldEntity.Id);
|
||||
Assert.False(controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
controller.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActivePlacement_IsGenerationScopedAndClearsOnTeardownAndReset()
|
||||
{
|
||||
|
|
@ -260,6 +646,7 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
public ShadowObjectRegistry Shadows { get; } = new();
|
||||
public WorldEntity Entity { get; }
|
||||
public LiveEntityPresentationController Controller { get; }
|
||||
public Action<uint>? OnTypedPlay { get; set; }
|
||||
|
||||
public Fixture(
|
||||
PhysicsStateFlags initialState,
|
||||
|
|
@ -306,6 +693,7 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
{
|
||||
TypedPlays.Add((owner, type, intensity));
|
||||
PresentationOrder.Add($"effect:{type:X2}");
|
||||
OnTypedPlay?.Invoke(type);
|
||||
return true;
|
||||
},
|
||||
(parent, noDraw) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue