feat(streaming): shadow-publish flat collision assets
Carry one immutable prepared collision closure with each accepted near-tier generation and install graph plus flat views through the same retained publication receipt. Apply the same strict package-only rule to live entities, add exact sampled graph-authoritative comparison artifacts and lifecycle counters, and prove cancellation, demotion, rehydrate, revisit, teardown, reconnect, and the nine-stop route with 14,064 zero-mismatch samples. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
f7ff9f4eea
commit
d9446030e6
32 changed files with 2777 additions and 92 deletions
294
tests/AcDream.Core.Tests/Physics/CollisionShadowVerifierTests.cs
Normal file
294
tests/AcDream.Core.Tests/Physics/CollisionShadowVerifierTests.cs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
public sealed class CollisionShadowVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
public void MatchingDualView_RefereesEveryTraversalWithoutChangingGraph()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics cell = MatchingCell(0xA9B4_0100u);
|
||||
cache.RegisterCellStructForTest(cell.SourceId, cell);
|
||||
var transition = SeedTransition();
|
||||
|
||||
Assert.True(CollisionTraversal.HasCellContainment(cache, cell));
|
||||
Assert.True(CollisionTraversal.HasPhysics(cache, cell));
|
||||
FlatCollisionSphere root =
|
||||
CollisionTraversal.RootBoundingSphere(cache, cell);
|
||||
Assert.Equal(10f, root.Radius);
|
||||
Assert.True(CollisionTraversal.PointInsideCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero));
|
||||
Assert.True(CollisionTraversal.SphereIntersectsCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero,
|
||||
0.48f));
|
||||
TransitionState state = CollisionTraversal.FindCollisions(
|
||||
cache,
|
||||
cell,
|
||||
transition,
|
||||
Vector3.Zero,
|
||||
0.48f,
|
||||
false,
|
||||
Vector3.Zero,
|
||||
0f,
|
||||
Vector3.Zero,
|
||||
Vector3.UnitZ,
|
||||
1f,
|
||||
Quaternion.Identity,
|
||||
engine: null,
|
||||
worldOrigin: Vector3.Zero);
|
||||
|
||||
Assert.Equal(TransitionState.OK, state);
|
||||
CollisionShadowStats stats = cache.CollisionShadowStats;
|
||||
Assert.Equal(6, stats.Queries);
|
||||
Assert.Equal(6, stats.Samples);
|
||||
Assert.Equal(6, stats.Matches);
|
||||
Assert.Equal(0, stats.Mismatches);
|
||||
Assert.Equal(0, stats.Faults);
|
||||
Assert.Empty(Directory.EnumerateFiles(directory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mismatch_WritesDeterministicArtifactAndReturnsGraphResult()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics matching = MatchingCell(0xA9B4_0100u);
|
||||
CellPhysics mismatched = WithFlatPhysics(
|
||||
matching,
|
||||
new FlatPhysicsBsp(
|
||||
-1,
|
||||
ImmutableArray<FlatPhysicsBspNode>.Empty,
|
||||
ImmutableArray<int>.Empty,
|
||||
FlatPolygonTable.Empty));
|
||||
|
||||
bool graphResult = CollisionTraversal.HasPhysics(cache, mismatched);
|
||||
|
||||
Assert.True(graphResult);
|
||||
CollisionShadowStats stats = cache.CollisionShadowStats;
|
||||
Assert.Equal(1, stats.Mismatches);
|
||||
Assert.Equal(0, stats.Faults);
|
||||
string artifact = Assert.Single(Directory.EnumerateFiles(directory));
|
||||
Assert.Equal(
|
||||
"collision-shadow-00000001.json",
|
||||
Path.GetFileName(artifact));
|
||||
using JsonDocument json = JsonDocument.Parse(File.ReadAllText(artifact));
|
||||
JsonElement root = json.RootElement;
|
||||
Assert.Equal(1, root.GetProperty("SchemaVersion").GetInt32());
|
||||
Assert.Equal("HasCellPhysics", root.GetProperty("Kind").GetString());
|
||||
Assert.Equal("0xA9B40100", root.GetProperty("SourceId").GetString());
|
||||
Assert.Equal("Result", root.GetProperty("Difference").GetString());
|
||||
Assert.Equal("True", root.GetProperty("Graph").GetString());
|
||||
Assert.Equal("False", root.GetProperty("Flat").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingFlat_IsRecordedAsFaultAndGraphRemainsAuthoritative()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics cell = WithoutFlatContainment(
|
||||
MatchingCell(0xA9B4_0100u));
|
||||
|
||||
bool graphResult = CollisionTraversal.PointInsideCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero);
|
||||
|
||||
Assert.True(graphResult);
|
||||
Assert.Equal(1, cache.CollisionShadowStats.Faults);
|
||||
string artifact = Assert.Single(Directory.EnumerateFiles(directory));
|
||||
Assert.Contains(
|
||||
"FlatFault",
|
||||
File.ReadAllText(artifact),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransitionCopyAndComparer_CoverCompleteLogicalState()
|
||||
{
|
||||
Transition source = SeedTransition();
|
||||
source.ObjectInfo.State =
|
||||
ObjectInfoState.Contact | ObjectInfoState.IsPlayer;
|
||||
source.ObjectInfo.TargetId = 0x5000_0001u;
|
||||
source.ObjectInfo.VelocityKilled = true;
|
||||
source.SpherePath.NumSphere = 2;
|
||||
source.SpherePath.LocalSphere[1].Origin = Vector3.UnitZ;
|
||||
source.SpherePath.LocalSphere[1].Radius = 0.48f;
|
||||
source.SpherePath.CarriedBlockOrigin = new Vector3(192f, -192f, 0f);
|
||||
source.SpherePath.SetWalkable(
|
||||
new Plane(Vector3.UnitZ, -1f),
|
||||
[
|
||||
Vector3.Zero,
|
||||
Vector3.UnitX,
|
||||
Vector3.UnitY,
|
||||
],
|
||||
Vector3.UnitZ);
|
||||
source.SpherePath.CellCandidates.Add(0xA9B4_0001u);
|
||||
source.SpherePath.CellCandidates.Add(0xA9B4_0100u);
|
||||
source.CollisionInfo.SetContactPlane(
|
||||
new Plane(Vector3.UnitZ, -1f),
|
||||
0xA9B4_0100u);
|
||||
source.CollisionInfo.SetSlidingNormal(Vector3.UnitX);
|
||||
source.CollisionInfo.CollideObjectGuids.Add(0x8000_0001u);
|
||||
source.CollisionInfo.LastCollidedObjectGuid = 0x8000_0001u;
|
||||
|
||||
var clone = new Transition();
|
||||
clone.CopyFrom(source);
|
||||
|
||||
Assert.False(TransitionExactComparer.TryFindDifference(
|
||||
source,
|
||||
clone,
|
||||
out _,
|
||||
out _,
|
||||
out _));
|
||||
clone.SpherePath.WalkableVertices![1].X =
|
||||
BitConverter.Int32BitsToSingle(
|
||||
BitConverter.SingleToInt32Bits(
|
||||
clone.SpherePath.WalkableVertices[1].X) + 1);
|
||||
Assert.True(TransitionExactComparer.TryFindDifference(
|
||||
source,
|
||||
clone,
|
||||
out string path,
|
||||
out _,
|
||||
out _));
|
||||
Assert.Equal("SpherePath.WalkableVertices[1]", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledGraphTraversal_AllocatesZeroBytes()
|
||||
{
|
||||
var cache = new PhysicsDataCache
|
||||
{
|
||||
CollisionShadow = null,
|
||||
};
|
||||
CellPhysics cell = MatchingCell(0xA9B4_0100u);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
_ = CollisionTraversal.HasPhysics(cache, cell);
|
||||
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int i = 0; i < 10_000; i++)
|
||||
_ = CollisionTraversal.HasPhysics(cache, cell);
|
||||
long allocated =
|
||||
GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
}
|
||||
|
||||
private static PhysicsDataCache CacheWithShadow(string directory)
|
||||
{
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.CollisionShadow = new CollisionShadowVerifier(1, directory);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static CellPhysics MatchingCell(uint sourceId)
|
||||
{
|
||||
var physicsRoot = new PhysicsBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 10f,
|
||||
},
|
||||
};
|
||||
var containmentRoot = new CellBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
LeafIndex = 1,
|
||||
};
|
||||
var resolved = new Dictionary<ushort, ResolvedPolygon>();
|
||||
return new CellPhysics
|
||||
{
|
||||
SourceId = sourceId,
|
||||
BSP = new PhysicsBSPTree { Root = physicsRoot },
|
||||
Resolved = resolved,
|
||||
CellBSP = new CellBSPTree { Root = containmentRoot },
|
||||
FlatPhysicsBsp =
|
||||
FlatCollisionAssetBuilder.FlattenPhysicsBsp(
|
||||
physicsRoot,
|
||||
resolved),
|
||||
FlatContainmentBsp =
|
||||
FlatCollisionAssetBuilder.FlattenCellContainmentBsp(
|
||||
containmentRoot),
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
};
|
||||
}
|
||||
|
||||
private static Transition SeedTransition()
|
||||
{
|
||||
var transition = new Transition();
|
||||
transition.SpherePath.InitPath(
|
||||
Vector3.Zero,
|
||||
Vector3.Zero,
|
||||
0xA9B4_0100u,
|
||||
0.48f);
|
||||
return transition;
|
||||
}
|
||||
|
||||
private static CellPhysics WithFlatPhysics(
|
||||
CellPhysics source,
|
||||
FlatPhysicsBsp flat) => new()
|
||||
{
|
||||
SourceId = source.SourceId,
|
||||
BSP = source.BSP,
|
||||
PhysicsPolygons = source.PhysicsPolygons,
|
||||
Vertices = source.Vertices,
|
||||
WorldTransform = source.WorldTransform,
|
||||
InverseWorldTransform = source.InverseWorldTransform,
|
||||
Resolved = source.Resolved,
|
||||
FlatPhysicsBsp = flat,
|
||||
CellBSP = source.CellBSP,
|
||||
FlatContainmentBsp = source.FlatContainmentBsp,
|
||||
FlatPortalPolygons = source.FlatPortalPolygons,
|
||||
FlatTopology = source.FlatTopology,
|
||||
Portals = source.Portals,
|
||||
PortalPolygons = source.PortalPolygons,
|
||||
VisibleCellIds = source.VisibleCellIds,
|
||||
SeenOutside = source.SeenOutside,
|
||||
};
|
||||
|
||||
private static CellPhysics WithoutFlatContainment(
|
||||
CellPhysics source) => new()
|
||||
{
|
||||
SourceId = source.SourceId,
|
||||
BSP = source.BSP,
|
||||
PhysicsPolygons = source.PhysicsPolygons,
|
||||
Vertices = source.Vertices,
|
||||
WorldTransform = source.WorldTransform,
|
||||
InverseWorldTransform = source.InverseWorldTransform,
|
||||
Resolved = source.Resolved,
|
||||
FlatPhysicsBsp = source.FlatPhysicsBsp,
|
||||
CellBSP = source.CellBSP,
|
||||
FlatContainmentBsp = null,
|
||||
FlatPortalPolygons = source.FlatPortalPolygons,
|
||||
FlatTopology = source.FlatTopology,
|
||||
Portals = source.Portals,
|
||||
PortalPolygons = source.PortalPolygons,
|
||||
VisibleCellIds = source.VisibleCellIds,
|
||||
SeenOutside = source.SeenOutside,
|
||||
};
|
||||
|
||||
private static string NewArtifactDirectory()
|
||||
{
|
||||
string directory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"acdream-collision-shadow-tests",
|
||||
Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue