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:
Erik 2026-07-25 16:38:54 +02:00
parent f7ff9f4eea
commit d9446030e6
32 changed files with 2777 additions and 92 deletions

View 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;
}
}