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

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using System.Numerics;
using System.Reflection;
using AcDream.App.Streaming;
@ -94,6 +95,254 @@ public sealed class LandblockPhysicsPublisherTests
Assert.Equal(0x01000077u, building.ModelId);
}
[Fact]
public void DualPublication_InstallsExactPreparedClosureAndReleasesCellOwner()
{
const uint envCellId = 0xA9B40100u;
const uint gfxObjId = 0x01000077u;
var fixture = Fixture();
PhysicsDatBundle baseBundle =
CellPortalAndBuildingBundle(envCellId);
DatReaderWriter.DBObjs.Environment environment =
Assert.Single(baseBundle.Environments.Values);
CellStruct cellStruct = Assert.Single(environment.Cells.Values);
cellStruct.PhysicsBSP = new PhysicsBSPTree
{
Root = new PhysicsBSPNode
{
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
BoundingSphere = new Sphere
{
Origin = Vector3.Zero,
Radius = 10f,
},
},
};
cellStruct.CellBSP = new CellBSPTree
{
Root = new CellBSPNode
{
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
LeafIndex = 1,
},
};
EnvCell envCell = baseBundle.EnvCells[envCellId];
var setup = new Setup();
setup.CylSpheres.Add(new CylSphere
{
Origin = new Vector3(0f, 0f, 0.6f),
Radius = 0.4f,
Height = 1.2f,
});
GfxObj gfx = PhysicsGfx();
var bundle = new PhysicsDatBundle(
baseBundle.Info,
baseBundle.EnvCells,
baseBundle.Environments,
new Dictionary<uint, Setup> { [SetupId] = setup },
new Dictionary<uint, GfxObj> { [gfxObjId] = gfx });
FlatGfxObjCollisionAsset flatGfx =
FlatCollisionAssetBuilder.FlattenGfxObj(gfx);
FlatSetupCollision flatSetup =
FlatCollisionAssetBuilder.FlattenSetup(setup);
FlatCellStructureCollisionAsset flatCell =
FlatCollisionAssetBuilder.FlattenCellStructure(cellStruct);
FlatEnvCellTopology flatTopology =
FlatCollisionAssetBuilder.FlattenEnvCellTopology(
envCellId,
envCell,
flatCell.PortalPolygons);
var collisions = new LandblockCollisionBuild(
ImmutableDictionary<uint, FlatGfxObjCollisionAsset>.Empty
.Add(gfxObjId, flatGfx),
ImmutableDictionary<uint, FlatSetupCollision>.Empty
.Add(SetupId, flatSetup),
ImmutableDictionary<uint, FlatCellStructureCollisionAsset>.Empty
.Add(envCellId, flatCell),
ImmutableDictionary<uint, FlatEnvCellTopology>.Empty
.Add(envCellId, flatTopology),
[gfxObjId],
[SetupId],
[envCellId]);
WorldEntity entity = new()
{
Id = 0x80A9B401u,
SourceGfxObjOrSetupId = SetupId,
Position = new Vector3(12f, 12f, 0f),
Rotation = Quaternion.Identity,
MeshRefs = [new MeshRef(gfxObjId, Matrix4x4.Identity)],
};
Publish(
fixture.Publisher,
Build(
FirstLandblock,
[entity],
bundle,
collisions: collisions));
Assert.Same(flatGfx, fixture.Cache.GetFlatGfxObj(gfxObjId));
Assert.Same(
flatGfx.PhysicsBsp,
fixture.Cache.GetGfxObj(gfxObjId)!.FlatPhysicsBsp);
Assert.Same(flatSetup, fixture.Cache.GetFlatSetup(SetupId));
Assert.Same(
flatSetup,
fixture.Cache.GetSetup(SetupId)!.FlatCollision);
Assert.Same(flatCell, fixture.Cache.GetFlatCellStruct(envCellId));
Assert.Same(flatTopology, fixture.Cache.GetFlatEnvCell(envCellId));
Assert.Same(
flatCell.PhysicsBsp,
fixture.Cache.GetCellStruct(envCellId)!.FlatPhysicsBsp);
fixture.Publisher.RemoveLandblock(FirstLandblock);
Assert.Equal(0, fixture.Cache.CellStructCount);
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
Assert.Equal(0, fixture.Engine.ShadowObjects.RetainedRegistrationCount);
}
[Fact]
public void DualPublication_DemotionRehydrateRevisitAndRemovalOwnFlatCellsExactlyOnce()
{
const uint firstCellId = 0xA9B40100u;
const uint adjacentCellId = 0xAAB40100u;
var fixture = Fixture();
PhysicsDatBundle firstBundle =
CellPortalAndBuildingBundle(firstCellId);
PhysicsDatBundle adjacentBundle =
CellPortalAndBuildingBundle(adjacentCellId);
LandblockCollisionBuild firstCollision =
FlatCellClosure(firstBundle, firstCellId);
LandblockCollisionBuild adjacentCollision =
FlatCellClosure(adjacentBundle, adjacentCellId);
Publish(
fixture.Publisher,
Build(
FirstLandblock,
bundle: firstBundle,
collisions: firstCollision));
Publish(
fixture.Publisher,
Build(
AdjacentLandblock,
bundle: adjacentBundle,
collisions: adjacentCollision));
Assert.Same(
firstCollision.CellStructures[firstCellId],
fixture.Cache.GetFlatCellStruct(firstCellId));
Assert.Same(
adjacentCollision.CellStructures[adjacentCellId],
fixture.Cache.GetFlatCellStruct(adjacentCellId));
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
fixture.Publisher.DemoteToTerrain(FirstLandblock);
Assert.Null(fixture.Cache.GetFlatCellStruct(firstCellId));
Assert.Null(fixture.Cache.GetFlatEnvCell(firstCellId));
Assert.Same(
adjacentCollision.CellStructures[adjacentCellId],
fixture.Cache.GetFlatCellStruct(adjacentCellId));
Assert.Equal(1, fixture.Cache.FlatCellStructCount);
Assert.Equal(1, fixture.Cache.FlatEnvCellCount);
LandblockCollisionBuild rehydratedCollision =
FlatCellClosure(firstBundle, firstCellId);
Publish(
fixture.Publisher,
Build(
FirstLandblock,
bundle: firstBundle,
collisions: rehydratedCollision));
Assert.Same(
rehydratedCollision.CellStructures[firstCellId],
fixture.Cache.GetFlatCellStruct(firstCellId));
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
LandblockCollisionBuild revisitCollision =
FlatCellClosure(firstBundle, firstCellId);
Publish(
fixture.Publisher,
Build(
FirstLandblock,
bundle: firstBundle,
collisions: revisitCollision));
Assert.Same(
revisitCollision.CellStructures[firstCellId],
fixture.Cache.GetFlatCellStruct(firstCellId));
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
fixture.Publisher.RemoveLandblock(FirstLandblock);
fixture.Publisher.RemoveLandblock(AdjacentLandblock);
Assert.Equal(0, fixture.Cache.CellStructCount);
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
Assert.Equal(0, fixture.Engine.LandblockCount);
}
[Fact]
public void DualPublication_CancelledPreparedReceiptPublishesNeitherView()
{
const uint envCellId = 0xA9B40100u;
var fixture = Fixture();
PhysicsDatBundle bundle =
CellPortalAndBuildingBundle(envCellId);
CellStruct sourceCell =
bundle.Environments.Values.Single().Cells.Values.Single();
sourceCell.PhysicsBSP = new PhysicsBSPTree
{
Root = new PhysicsBSPNode
{
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
BoundingSphere = new Sphere
{
Origin = Vector3.Zero,
Radius = 10f,
},
},
};
LandblockCollisionBuild collision =
FlatCellClosure(bundle, envCellId);
LandblockPhysicsPublication cancelled =
fixture.Publisher.PreparePublication(
RenderReceipt(Build(
FirstLandblock,
bundle: bundle,
collisions: collision)));
Assert.Equal(0, fixture.Engine.LandblockCount);
Assert.Equal(0, fixture.Cache.CellStructCount);
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
Assert.Equal(0, fixture.Engine.ShadowObjects.RetainedRegistrationCount);
LandblockPhysicsPublication accepted =
fixture.Publisher.PreparePublication(
RenderReceipt(Build(
FirstLandblock,
bundle: bundle,
collisions: collision)));
fixture.Publisher.BeginPublication(accepted);
fixture.Publisher.CompletePublication(accepted);
Assert.Same(
collision.CellStructures[envCellId],
fixture.Cache.GetFlatCellStruct(envCellId));
Assert.Equal(1, fixture.Cache.CellStructCount);
Assert.Equal(1, fixture.Cache.FlatCellStructCount);
Assert.Equal(1, fixture.Cache.FlatEnvCellCount);
}
[Fact]
public void CompletePublication_PhysicsBspSuppressesSetupCylinderFallback()
{
@ -592,6 +841,25 @@ public sealed class LandblockPhysicsPublisherTests
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
};
private static GfxObj PhysicsGfx() => new()
{
Flags = DatReaderWriter.Enums.GfxObjFlags.HasPhysics,
PhysicsBSP = new PhysicsBSPTree
{
Root = new PhysicsBSPNode
{
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
BoundingSphere = new Sphere
{
Origin = Vector3.Zero,
Radius = 1.25f,
},
},
},
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
VertexArray = new VertexArray(),
};
private static PhysicsDatBundle CellPortalAndBuildingBundle(uint envCellId)
{
var cellStruct = new CellStruct
@ -658,6 +926,33 @@ public sealed class LandblockPhysicsPublisherTests
new Dictionary<uint, GfxObj>());
}
private static LandblockCollisionBuild FlatCellClosure(
PhysicsDatBundle bundle,
uint envCellId)
{
EnvCell envCell = bundle.EnvCells[envCellId];
DatReaderWriter.DBObjs.Environment environment =
bundle.Environments.Values.Single();
CellStruct cellStruct = environment.Cells[envCell.CellStructure];
FlatCellStructureCollisionAsset flatCell =
FlatCollisionAssetBuilder.FlattenCellStructure(cellStruct);
FlatEnvCellTopology topology =
FlatCollisionAssetBuilder.FlattenEnvCellTopology(
envCellId,
envCell,
flatCell.PortalPolygons);
return new LandblockCollisionBuild(
ImmutableDictionary<uint, FlatGfxObjCollisionAsset>.Empty,
ImmutableDictionary<uint, FlatSetupCollision>.Empty,
ImmutableDictionary<uint, FlatCellStructureCollisionAsset>.Empty
.Add(envCellId, flatCell),
ImmutableDictionary<uint, FlatEnvCellTopology>.Empty
.Add(envCellId, topology),
[],
[],
[envCellId]);
}
private static WorldEntity CylinderEntity(
uint id,
Vector3 position,
@ -674,7 +969,8 @@ public sealed class LandblockPhysicsPublisherTests
uint landblockId,
IReadOnlyList<WorldEntity>? entities = null,
PhysicsDatBundle? bundle = null,
LandblockBuildOrigin? origin = null)
LandblockBuildOrigin? origin = null,
LandblockCollisionBuild? collisions = null)
{
var landblockInfo = new LandBlockInfo();
PhysicsDatBundle physics = bundle ?? new PhysicsDatBundle(
@ -689,7 +985,8 @@ public sealed class LandblockPhysicsPublisherTests
FlatHeightmap(),
entities ?? Array.Empty<WorldEntity>(),
physics),
Origin: origin ?? new LandblockBuildOrigin(0xA9, 0xB4));
Origin: origin ?? new LandblockBuildOrigin(0xA9, 0xB4),
Collisions: collisions);
}
private static LandBlock FlatHeightmap() => new()