feat(runtime): own deferred set-position residence

This commit is contained in:
Erik 2026-07-31 22:32:49 +02:00
parent e84a388e6f
commit 4c02ac4259
18 changed files with 5557 additions and 34 deletions

View file

@ -0,0 +1,167 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Core.Physics;
namespace AcDream.Core.Tests.Physics;
public sealed class ShadowSetPositionCommitTests
{
private const uint Landblock = 0xA9B40000u;
private const uint Cell1 = Landblock | 0x0001u;
private const uint Cell9 = Landblock | 0x0009u;
[Fact]
public void NoneRefreshesFrameWithoutChangingExactMembership()
{
var registry = RegisteredSingle();
ulong version = registry.GetOwnerVersion(1u);
var moved = new Vector3(14f, 12f, 50f);
registry.CommitSetPosition(
1u, moved, Quaternion.Identity, Cell1, 0f, 0f,
PhysicsShadowCommitAction.None, []);
ShadowEntry entry = Assert.Single(registry.GetObjectsInCell(Cell1));
Assert.Equal(moved, entry.Position);
Assert.Empty(registry.GetObjectsInCell(Cell9));
Assert.Equal(version + 1UL, registry.GetOwnerVersion(1u));
}
[Fact]
public void ReplaceDeduplicatesExactCellsAndPreserveRetainsThem()
{
var registry = RegisteredSingle();
var replaced = new Vector3(36f, 12f, 50f);
registry.CommitSetPosition(
1u, replaced, Quaternion.Identity, Cell9, 0f, 0f,
PhysicsShadowCommitAction.Replace,
ImmutableArray.Create(Cell9, Cell9, 0u));
Assert.Empty(registry.GetObjectsInCell(Cell1));
Assert.Equal(replaced,
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
var preserved = new Vector3(38f, 12f, 50f);
registry.CommitSetPosition(
1u, preserved, Quaternion.Identity, Cell9, 0f, 0f,
PhysicsShadowCommitAction.Preserve, []);
Assert.Equal(preserved,
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
var emptyReplace = new Vector3(40f, 12f, 50f);
registry.CommitSetPosition(
1u, emptyReplace, Quaternion.Identity, Cell9, 0f, 0f,
PhysicsShadowCommitAction.Replace, []);
Assert.Equal(emptyReplace,
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
}
[Fact]
public void RecalculateUsesCanonicalFloodInsteadOfRetainedCells()
{
var registry = RegisteredSingle();
var moved = new Vector3(36f, 12f, 50f);
registry.CommitSetPosition(
1u, moved, Quaternion.Identity, Cell9, 0f, 0f,
PhysicsShadowCommitAction.Recalculate, []);
Assert.Empty(registry.GetObjectsInCell(Cell1));
Assert.Equal(moved,
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
}
[Fact]
public void SuspendedPreserveRestoresExactRowsAndConsumesReceipt()
{
var registry = RegisteredSingle();
Assert.True(registry.Suspend(1u));
Assert.Equal(0, registry.TotalRegistered);
Assert.Equal(1, registry.SuspendedRegistrationCount);
var moved = new Vector3(15f, 12f, 50f);
registry.CommitSetPosition(
1u, moved, Quaternion.Identity, Cell1, 0f, 0f,
PhysicsShadowCommitAction.Preserve, []);
Assert.Equal(1, registry.TotalRegistered);
Assert.Equal(0, registry.SuspendedRegistrationCount);
Assert.Equal(moved,
Assert.Single(registry.GetObjectsInCell(Cell1)).Position);
}
[Fact]
public void SuspendedReceiptCopiesAcrossCollisionRootAndClearsOnTeardown()
{
var source = RegisteredSingle();
Assert.True(source.Suspend(1u));
var destination = new ShadowObjectRegistry();
Assert.False(destination.RefreshRetainedOwnerFrom(
source, 1u, Landblock, out ulong version));
Assert.Equal(source.GetOwnerVersion(1u), version);
Assert.Equal(1, destination.RetainedRegistrationCount);
Assert.Equal(1, destination.SuspendedRegistrationCount);
destination.CommitSetPosition(
1u, new Vector3(16f, 12f, 50f), Quaternion.Identity,
Cell1, 0f, 0f, PhysicsShadowCommitAction.None, []);
Assert.Single(destination.GetObjectsInCell(Cell1));
destination.Deregister(1u);
Assert.Equal(0, destination.RetainedRegistrationCount);
Assert.Equal(0, destination.SuspendedRegistrationCount);
destination.Clear();
Assert.Equal(0, destination.TotalRegistered);
}
[Fact]
public void MultiPartNoneRefreshesEachPartFromRootTransform()
{
var registry = new ShadowObjectRegistry();
IReadOnlyList<ShadowShape> shapes =
[
Shape(0x01000001u, new Vector3(1f, 0f, 0f)),
Shape(0x01000002u, new Vector3(0f, 1f, 0f)),
];
registry.RegisterMultiPart(
2u, new Vector3(12f, 12f, 50f), Quaternion.Identity,
shapes, 0u, EntityCollisionFlags.None, 0f, 0f, Landblock,
Cell1);
Quaternion rotation = Quaternion.CreateFromAxisAngle(
Vector3.UnitZ, MathF.PI / 2f);
var root = new Vector3(14f, 12f, 50f);
registry.CommitSetPosition(
2u, root, rotation, Cell1, 0f, 0f,
PhysicsShadowCommitAction.None, []);
ShadowEntry[] entries = registry.GetObjectsInCell(Cell1)
.Where(entry => entry.EntityId == 2u)
.OrderBy(entry => entry.GfxObjId)
.ToArray();
Assert.Equal(2, entries.Length);
Assert.Equal(root + Vector3.Transform(shapes[0].LocalPosition, rotation),
entries[0].Position);
Assert.Equal(root + Vector3.Transform(shapes[1].LocalPosition, rotation),
entries[1].Position);
}
private static ShadowObjectRegistry RegisteredSingle()
{
var registry = new ShadowObjectRegistry();
registry.Register(
1u, 0x01000001u, new Vector3(12f, 12f, 50f),
Quaternion.Identity, 1f, 0f, 0f, Landblock,
seedCellId: Cell1, isStatic: false);
return registry;
}
private static ShadowShape Shape(uint gfxObjId, Vector3 local) => new(
gfxObjId,
local,
Quaternion.Identity,
1f,
ShadowCollisionType.BSP,
0.25f,
0f);
}