diff --git a/docs/research/2026-09-01-overhaul/c1a-geometry-witness-integration-contract.md b/docs/research/2026-09-01-overhaul/c1a-geometry-witness-integration-contract.md
index 38f63a09..06f785e3 100644
--- a/docs/research/2026-09-01-overhaul/c1a-geometry-witness-integration-contract.md
+++ b/docs/research/2026-09-01-overhaul/c1a-geometry-witness-integration-contract.md
@@ -51,3 +51,46 @@ The fresh initialized0..6 part-order assertion is a production initialization
witness, not a claim of universal equality with later live-retail retained
part-list order0,1,6,5,2,4,3. AD117's source approximation and AP159's boundary
ties remain explicit. C1a/G4 unpassed, FPS deferred, known retail leak unchanged.
+
+## Write-only return — 2026-09-05
+
+The test is code-ready in the campaign tree. Lead independently compared it
+against the preserved scratch source: exactly eight added lines at actual
+ShellCellId mesh extraction, no other change. Campaign source SHA256
+4CA55ED6033D99447E367577EFAE307E34516BDF75245A5C591B007D1F5DAC0B;
+original scratch SHA9894EC50BF40F732237E3249E5F7815FDC931DDC66F1D5D212D0CA717373A9C5
+unchanged. Diff-check clean. No AuthoredDiagnostic copied; placeholder and
+three-cell/21-entry assertions remain. This is an implementation return,
+not independent review approval or execution evidence. No builds/tests or
+clients were run under this write-only release. Bridge implementer retains
+exclusive execution ownership until its frozen return; the later execution
+boundary above is still closed.
+
+## Lead capture release — after bridge1e645ee40, 2026-09-05
+
+Bridge implementation is committed1e645ee40 with its narrowed register row,
+managed integrity adaptation and architecture/ledger. Independent bounded
+review PASS; exact restored affected52/52 and source hashes verified by lead.
+Implementer returned execution ownership; no live build/test handle remains.
+
+Lead may now commit this guarded original witness and run one Release App
+test-project build, then exactly
+`CathedralFactoryGeometryAndRetailMembership_AreCompleteAndDeterministic`
+once with ACDREAM_RUN_INSTALLED_DAT_TESTS=1 and explicit
+`C:/Users/erikn/Documents/Asheron's Call`. Recheck both clients/debugger
+absent and ACE UDP9000 first; no agent owns concurrent execution. Preserve
+build/stdout/TRX in new `artifacts/s5-c1a-integrated-capture-20260905-r1/`.
+No original or authored diagnostic is included in that exact filter.
+
+The expected first failure is ONLY the unpinned combined-hash assertion,
+after all real semantic facts and both fresh owner products agree. Inspect
+the exact failure location, all four DAT hashes, three product hashes/counts
+and raw byte-equality assertions before accepting that result. A different
+failure is a bounded finding; do not change the three-cell/21-entry oracle,
+production or any predicate to make this test pass. No new numerical retry
+or authored-diagnostic fix belongs to this release.
+
+Pinning the observed combined hash and the later product mutations require
+their recorded lead release after this capture. Full canonical/hermetic,
+two clean-checkpoint focused processes and final graphical gates remain owed.
+No C1a/G4 pass, FPS work or known retail leak change is authorized here.
diff --git a/tests/AcDream.App.Tests/Streaming/LandblockGeometryMembershipInstalledDatTests.cs b/tests/AcDream.App.Tests/Streaming/LandblockGeometryMembershipInstalledDatTests.cs
new file mode 100644
index 00000000..df0ccdbc
--- /dev/null
+++ b/tests/AcDream.App.Tests/Streaming/LandblockGeometryMembershipInstalledDatTests.cs
@@ -0,0 +1,1244 @@
+using System.Collections.Immutable;
+using System.Numerics;
+using System.Reflection;
+using System.Security.Cryptography;
+using AcDream.App.Rendering;
+using AcDream.App.Rendering.Walk;
+using AcDream.App.Rendering.Wb;
+using AcDream.App.Streaming;
+using AcDream.Content;
+using AcDream.Content.Pak;
+using AcDream.Core.Meshing;
+using AcDream.Core.Physics;
+using AcDream.Core.World;
+using Chorizite.Core.Render.Enums;
+using DatReaderWriter.DBObjs;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit.Abstractions;
+using DatEnvironment = DatReaderWriter.DBObjs.Environment;
+using DatEnvCell = DatReaderWriter.DBObjs.EnvCell;
+using DatGfxObj = DatReaderWriter.DBObjs.GfxObj;
+using DatSetup = DatReaderWriter.DBObjs.Setup;
+
+namespace AcDream.App.Tests.Streaming;
+
+///
+/// C1a's installed-retail-DAT witness for the complete F418 geometry and
+/// render-membership product. This deliberately consumes the real worker
+/// factory, live-DAT extractor, flat collision builder, publication helpers,
+/// production cache, and production shadow registry. It contains no second
+/// membership implementation or synthetic expected entry list.
+///
+[Trait("Lane", "InstalledDat")]
+public sealed partial class LandblockGeometryMembershipInstalledDatTests
+{
+ private const uint LandblockId = 0xF418FFFFu;
+ private const uint ShellCellId = 0xF4180104u;
+ private const uint RampSetupId = 0x020009A2u;
+ private const string ExpectedCombinedHash = "REPLACE_AFTER_FIRST_VERIFIED_CAPTURE";
+
+ private static readonly (string Name, string Sha256)[] AcceptedDatFiles =
+ [
+ ("client_portal.dat", "DC6E500BA22E6B186DB7171E3F3345238B6444C85D798ADC85E550973B8D12E4"),
+ ("client_cell_1.dat", "6DB0ABF00FBCEED62C3F1EE842EE7C1F423D732BED77A5B7C102EE89A52AB99E"),
+ ("client_highres.dat", "503E0828D14F2F9CCBC31431E1055AC188464BF4B499DE37F4C3D5B2D9F3E727"),
+ ("client_local_English.dat", "E85C820280C88FAC7DF6C8043F5E24596E9C8774193AF4123D756546F78FB2BB"),
+ ];
+
+ private readonly ITestOutputHelper _output;
+
+ public LandblockGeometryMembershipInstalledDatTests(ITestOutputHelper output) =>
+ _output = output;
+
+ [Fact]
+ public void CathedralFactoryGeometryAndRetailMembership_AreCompleteAndDeterministic()
+ {
+ string datDirectory = RequireExplicitInstalledDatDirectory();
+ VerifyInstalledFileIdentity(datDirectory);
+
+ Capture first = CaptureProduct(datDirectory);
+ Capture second = CaptureProduct(datDirectory);
+
+ _output.WriteLine($"geometry-sha256={first.GeometryHash}");
+ _output.WriteLine($"membership-sha256={first.MembershipHash}");
+ _output.WriteLine($"combined-sha256={first.CombinedHash}");
+ _output.WriteLine($"counts={first.Counts}");
+
+ Assert.Equal(first.GeometryHash, second.GeometryHash);
+ Assert.Equal(first.MembershipHash, second.MembershipHash);
+ Assert.Equal(first.CombinedHash, second.CombinedHash);
+ Assert.Equal(first.Counts, second.Counts);
+ Assert.Equal(first.GeometryBytes, second.GeometryBytes);
+ Assert.Equal(first.MembershipBytes, second.MembershipBytes);
+ Assert.Equal(ExpectedCombinedHash, first.CombinedHash);
+ }
+
+ [Fact]
+ [Trait("Purpose", "Diagnostic")]
+ public void CathedralRampBuildingBridge_ReportsFiniteRetailBoxPredicates()
+ {
+ string datDirectory = RequireExplicitInstalledDatDirectory();
+ VerifyInstalledFileIdentity(datDirectory);
+
+ _ = WithPublishedProduct(
+ datDirectory,
+ (build, _, cache, engine, _) =>
+ {
+ ReportRampBuildingBridge(build, cache, engine);
+ return true;
+ });
+ }
+
+ private void VerifyInstalledFileIdentity(string datDirectory)
+ {
+ foreach ((string name, string accepted) in AcceptedDatFiles)
+ {
+ string path = Path.Combine(datDirectory, name);
+ Assert.True(File.Exists(path), $"Installed DAT is missing: '{path}'.");
+ using FileStream stream = File.OpenRead(path);
+ string actual = Convert.ToHexString(SHA256.HashData(stream));
+ _output.WriteLine($"dat-sha256 {name}={actual}");
+ Assert.Equal(accepted, actual);
+ }
+ }
+
+ private static string RequireExplicitInstalledDatDirectory()
+ {
+ Assert.Equal(
+ "1",
+ System.Environment.GetEnvironmentVariable("ACDREAM_RUN_INSTALLED_DAT_TESTS"));
+ string? directory = System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
+ Assert.False(
+ string.IsNullOrWhiteSpace(directory),
+ "C1a requires an explicit ACDREAM_DAT_DIR for its installed-DAT identity gate.");
+ Assert.True(Directory.Exists(directory), $"Installed DAT directory is missing: '{directory}'.");
+ return directory!;
+ }
+
+ private static Capture CaptureProduct(string datDirectory) =>
+ WithPublishedProduct(
+ datDirectory,
+ static (build, envBuild, _, engine, meshSource) =>
+ {
+ ProductFacts facts = AssertSemanticFacts(build, envBuild, engine);
+ (byte[] geometry, GeometryCounts geometryCounts) =
+ SerializeGeometry(build, envBuild, meshSource);
+ (byte[] membership, MembershipCounts membershipCounts) =
+ SerializeMembership(build, envBuild, engine, facts);
+ byte[] combined = SerializeCombined(geometry, membership);
+
+ return new Capture(
+ geometry,
+ membership,
+ Hash(geometry),
+ Hash(membership),
+ Hash(combined),
+ new ProductCounts(geometryCounts, membershipCounts));
+ });
+
+ private static T WithPublishedProduct(
+ string datDirectory,
+ Func consume)
+ {
+ using var dat = new BoundedTestDatCollection(datDirectory);
+ var bounded = (IDatReaderWriter)dat;
+ Region region = Assert.IsType(bounded.Get(0x13000000u));
+ float[] heights = region.LandDefs.LandHeightTable;
+ Assert.True(heights.Length >= 256);
+
+ using var collisionSource = new InstalledDatPreparedCollisionAdapter(bounded);
+ using var meshSource = new DatPreparedAssetSource(bounded, NullLogger.Instance);
+ var factory = new LandblockBuildFactory(
+ bounded,
+ collisionSource,
+ new object(),
+ heights);
+ var request = new LandblockBuildRequest(
+ LandblockId,
+ LandblockStreamJobKind.LoadNear,
+ Generation: 1,
+ new LandblockBuildOrigin(0xF4, 0x18));
+
+ LandblockBuild build = Assert.IsType(factory.Build(request));
+ EnvCellLandblockBuild envBuild = Assert.IsType(build.EnvCells);
+ LandblockCollisionBuild collisions = Assert.IsType(build.Collisions);
+ Assert.Equal(Vector3.Zero, PublicationOrigin(build.Origin));
+
+ var cache = PhysicsDataCache.CreateProduction();
+ var engine = new PhysicsEngine { DataCache = cache };
+ Vector3 origin = PublicationOrigin(build.Origin);
+ TerrainSurface terrain = LandblockPhysicsContentBuilder.BuildTerrainSurface(
+ build.Landblock,
+ heights);
+ var cellSurfaces = new List();
+ var portalPlanes = new List();
+ LandblockPhysicsContentBuilder.PublishPreparedCells(
+ cache,
+ build.Landblock,
+ collisions,
+ origin,
+ cellSurfaces,
+ portalPlanes);
+ LandblockPhysicsContentBuilder.CacheBuildings(
+ cache,
+ build.Landblock,
+ terrain,
+ origin);
+ LandblockPhysicsContentBuilder.CachePreparedObjects(cache, collisions);
+ engine.AddLandblock(
+ build.LandblockId,
+ terrain,
+ cellSurfaces,
+ portalPlanes,
+ origin.X,
+ origin.Y);
+ _ = LandblockPhysicsContentBuilder.PublishStaticCollision(
+ engine,
+ cache,
+ build.Landblock,
+ collisions,
+ origin);
+
+ return consume(build, envBuild, cache, engine, meshSource);
+ }
+
+ private void ReportRampBuildingBridge(
+ LandblockBuild build,
+ PhysicsDataCache cache,
+ PhysicsEngine engine)
+ {
+ const uint outdoorCellId = 0xF4180009u;
+ const uint destinationCellId = 0xF4180114u;
+ const float epsilon = 0.000199999995f;
+
+ WorldEntity ramp = Assert.Single(
+ build.Landblock.Entities,
+ entity => !entity.IsBuildingShell
+ && entity.SourceGfxObjOrSetupId == RampSetupId);
+ IReadOnlyList retainedParts =
+ GetRetainedPartArray(engine.ShadowObjects, ramp.Id);
+ Assert.Equal(ramp.MeshRefs.Count, retainedParts.Count);
+
+ IReadOnlyList partBoxes = InvokePrivateStatic<
+ IReadOnlyList>(
+ typeof(ShadowObjectRegistry),
+ "BuildFloodPartBoxes",
+ ramp.Position,
+ ramp.Rotation,
+ retainedParts);
+ IReadOnlyList partSpheres =
+ InvokePrivateStatic>(
+ typeof(ShadowObjectRegistry),
+ "BuildBspPartSpheres",
+ ramp.Position,
+ ramp.Rotation,
+ retainedParts);
+ Assert.Equal(retainedParts.Count, partBoxes.Count);
+ Assert.Equal(retainedParts.Count, partSpheres.Count);
+
+ BuildingPhysics building = Assert.IsType(
+ cache.GetBuilding(outdoorCellId));
+ CellPhysics destination = Assert.IsType(
+ cache.GetCellStruct(destinationCellId));
+ (int BuildingPortalIndex, BldPortalInfo BuildingPortal)[] bridges =
+ building.Portals
+ .Select((portal, index) => (index, portal))
+ .Where(candidate => candidate.portal.OtherCellId == destinationCellId)
+ .ToArray();
+ Assert.NotEmpty(bridges);
+
+ foreach ((int BuildingPortalIndex, BldPortalInfo BuildingPortal) bridge in bridges)
+ {
+ Assert.True(bridge.BuildingPortal.OtherPortalId >= 0);
+ int reciprocalIndex = bridge.BuildingPortal.OtherPortalId;
+ Assert.InRange(reciprocalIndex, 0, destination.Portals.Count - 1);
+ PortalInfo reciprocal = destination.Portals[reciprocalIndex];
+ Plane portalPlane = ResolvePortalPlane(
+ destination,
+ reciprocalIndex,
+ reciprocal);
+ int rawPortalSide = reciprocal.PortalSide ? 1 : 0;
+
+ _output.WriteLine(
+ $"bridge owner=0x{ramp.Id:X8} setup=0x{RampSetupId:X8} " +
+ $"from=0x{outdoorCellId:X8} buildingPortal={bridge.BuildingPortalIndex} " +
+ $"to=0x{destinationCellId:X8} reciprocal={reciprocalIndex} " +
+ $"flags=0x{bridge.BuildingPortal.Flags:X4} reciprocalOther=0x{reciprocal.OtherCellId:X4} " +
+ $"reciprocalFlags=0x{reciprocal.Flags:X4} side={rawPortalSide}");
+ _output.WriteLine($"destination-world={FormatMatrix(destination.WorldTransform)}");
+ _output.WriteLine($"destination-inverse={FormatMatrix(destination.InverseWorldTransform)}");
+ _output.WriteLine(
+ $"portal-plane n={FormatVector(portalPlane.Normal)} d={FormatFloat(portalPlane.D)}");
+ _output.WriteLine(
+ "shared-primitives box-transform=ShadowPartBox.RefitToLocal " +
+ "box-containment=FlatBspQuery.BoxIntersectsCellBsp " +
+ "sphere-containment=FlatBspQuery.SphereIntersectsCellBsp");
+
+ FlatCellContainmentBsp containment = Assert.IsType(
+ destination.FlatContainmentBsp);
+ bool anyRetailBoxAdmit = false;
+ bool anyCurrentSphereAdmit = false;
+ for (int partIndex = 0; partIndex < retainedParts.Count; partIndex++)
+ {
+ ShadowShape shape = retainedParts[partIndex];
+ ShadowPartBox box = partBoxes[partIndex];
+ DatReaderWriter.Types.Sphere sphere = partSpheres[partIndex];
+ Vector3 localSphereCenter = Vector3.Transform(
+ sphere.Origin,
+ destination.InverseWorldTransform);
+ double spherePlaneDistance = DotWide(portalPlane, localSphereCenter);
+ float paddedRadius = sphere.Radius + epsilon;
+ bool cheapRejectPass = rawPortalSide == 1
+ ? spherePlaneDistance <= paddedRadius
+ : spherePlaneDistance >= -paddedRadius;
+ double cheapTieMargin = rawPortalSide == 1
+ ? Math.Abs(spherePlaneDistance - paddedRadius)
+ : Math.Abs(spherePlaneDistance + paddedRadius);
+
+ box.RefitToLocal(
+ destination.InverseWorldTransform,
+ out Vector3 localBoxMin,
+ out Vector3 localBoxMax);
+ RetailBoxClassification classification = ClassifyRetailBox(
+ portalPlane,
+ localBoxMin,
+ localBoxMax,
+ epsilon);
+ bool planeAdmit =
+ classification.Side == RetailPlaneSide.Crossing
+ || (int)classification.Side == rawPortalSide;
+ bool boxContainment = FlatBspQuery.BoxIntersectsCellBsp(
+ containment,
+ localBoxMin,
+ localBoxMax);
+ bool sphereContainment = FlatBspQuery.SphereIntersectsCellBsp(
+ containment,
+ localSphereCenter,
+ sphere.Radius);
+ bool retailBoxAdmit = cheapRejectPass && planeAdmit && boxContainment;
+ anyRetailBoxAdmit |= retailBoxAdmit;
+ anyCurrentSphereAdmit |= sphereContainment;
+
+ Assert.True(double.IsFinite(spherePlaneDistance));
+ Assert.True(double.IsFinite(cheapTieMargin));
+ Assert.NotEqual(0d, cheapTieMargin);
+ Assert.True(double.IsFinite(classification.EpsilonTieMargin));
+ Assert.NotEqual(0d, classification.EpsilonTieMargin);
+
+ _output.WriteLine(
+ $"buildingPortal={bridge.BuildingPortalIndex} part={partIndex} gfx=0x{shape.GfxObjId:X8} " +
+ $"sphereWorld={FormatVector(sphere.Origin)} radius={FormatFloat(sphere.Radius)} " +
+ $"sphereLocal={FormatVector(localSphereCenter)} " +
+ $"distance={spherePlaneDistance:R} padded={FormatFloat(paddedRadius)} " +
+ $"cheap={cheapRejectPass} cheapTieMargin={cheapTieMargin:R}");
+ _output.WriteLine(
+ $"buildingPortal={bridge.BuildingPortalIndex} part={partIndex} " +
+ $"boxLocal={FormatVector(box.LocalMin)}..{FormatVector(box.LocalMax)} " +
+ $"boxWorldPosition={FormatVector(box.WorldPosition)} " +
+ $"boxWorldRotation={FormatQuaternion(box.WorldRotation)} " +
+ $"destBox={FormatVector(localBoxMin)}..{FormatVector(localBoxMax)} " +
+ $"cornerSides={string.Join(',', classification.CornerSides)} " +
+ $"boxSide={classification.Side} cornerTieMargin={classification.EpsilonTieMargin:R} " +
+ $"planeAdmit={planeAdmit} boxContain={boxContainment} " +
+ $"retailBoxAdmit={retailBoxAdmit} currentSphereContain={sphereContainment}");
+ }
+
+ _output.WriteLine(
+ $"portal-summary buildingPortal={bridge.BuildingPortalIndex} " +
+ $"retailBoxAny={anyRetailBoxAdmit} " +
+ $"currentSphereAny={anyCurrentSphereAdmit}");
+ }
+
+ _output.WriteLine($"bridge-summary portals={bridges.Length}");
+
+ Assert.True(engine.ShadowObjects.TryGetRetailCellArray(ramp.Id, out var actualCells));
+ _output.WriteLine(
+ $"current-retained-cellarray={string.Join(',', actualCells.Select(id => $"0x{id:X8}"))}");
+ foreach (uint sourceCellId in actualCells
+ .TakeWhile(id => id != destinationCellId)
+ .Where(id => (id & 0xFFFFu) >= 0x0100u))
+ {
+ CellPhysics sourceCell = Assert.IsType(
+ cache.GetCellStruct(sourceCellId));
+ var candidates = new CellArray();
+ CellTransit.FindTransitCellsBox(
+ cache,
+ sourceCell,
+ sourceCellId,
+ partBoxes,
+ partSpheres,
+ candidates,
+ out bool exitOutside);
+ _output.WriteLine(
+ $"normal-box-source=0x{sourceCellId:X8} " +
+ $"adds0114={candidates.Contains(destinationCellId)} " +
+ $"exitOutside={exitOutside} " +
+ $"candidates={string.Join(',', candidates.OrderedIds.Select(id => $"0x{id:X8}"))}");
+ }
+ }
+
+ private static IReadOnlyList GetRetainedPartArray(
+ ShadowObjectRegistry registry,
+ uint entityId)
+ {
+ PropertyInfo property = Assert.IsAssignableFrom(
+ typeof(ShadowObjectRegistry).GetProperty(
+ "_entityRetailPartArrays",
+ BindingFlags.Instance | BindingFlags.NonPublic));
+ var arrays = Assert.IsType>>(
+ property.GetValue(registry));
+ return Assert.IsAssignableFrom>(arrays[entityId]);
+ }
+
+ private static T InvokePrivateStatic(
+ Type owner,
+ string methodName,
+ params object?[] arguments)
+ {
+ MethodInfo method = Assert.IsAssignableFrom(owner.GetMethod(
+ methodName,
+ BindingFlags.Static | BindingFlags.NonPublic));
+ return Assert.IsAssignableFrom(method.Invoke(null, arguments));
+ }
+
+ private static Plane ResolvePortalPlane(
+ CellPhysics cell,
+ int portalIndex,
+ PortalInfo portal)
+ {
+ MethodInfo method = Assert.IsAssignableFrom(
+ typeof(CellTransit).GetMethod(
+ "TryGetPortalPlane",
+ BindingFlags.Static | BindingFlags.NonPublic));
+ object?[] arguments = [cell, portalIndex, portal, default(Plane)];
+ Assert.True(Assert.IsType(method.Invoke(null, arguments)));
+ return Assert.IsType(arguments[3]);
+ }
+
+ private static RetailBoxClassification ClassifyRetailBox(
+ Plane plane,
+ Vector3 min,
+ Vector3 max,
+ float epsilon)
+ {
+ Vector3[] corners =
+ [
+ new(min.X, min.Y, min.Z),
+ new(max.X, max.Y, max.Z),
+ new(min.X, min.Y, max.Z),
+ new(min.X, max.Y, min.Z),
+ new(max.X, min.Y, min.Z),
+ new(max.X, min.Y, max.Z),
+ new(min.X, max.Y, max.Z),
+ new(max.X, max.Y, min.Z),
+ ];
+ var sides = new RetailPlaneSide[corners.Length];
+ double epsilonTieMargin = double.MaxValue;
+ for (int index = 0; index < corners.Length; index++)
+ {
+ double distance = DotWide(plane, corners[index]);
+ Assert.True(double.IsFinite(distance));
+ epsilonTieMargin = Math.Min(
+ epsilonTieMargin,
+ Math.Min(
+ Math.Abs(distance - epsilon),
+ Math.Abs(distance + epsilon)));
+ sides[index] = distance > epsilon
+ ? RetailPlaneSide.Positive
+ : distance < -epsilon
+ ? RetailPlaneSide.Negative
+ : RetailPlaneSide.InPlane;
+ }
+
+ RetailPlaneSide result = sides[0] == RetailPlaneSide.InPlane
+ ? RetailPlaneSide.Crossing
+ : sides.Skip(1).Any(side => side != sides[0])
+ ? RetailPlaneSide.Crossing
+ : sides[0];
+ return new RetailBoxClassification(result, sides, epsilonTieMargin);
+ }
+
+ private static double DotWide(Plane plane, Vector3 point) =>
+ (double)plane.Normal.X * point.X
+ + (double)plane.Normal.Y * point.Y
+ + (double)plane.Normal.Z * point.Z
+ + plane.D;
+
+ private static string FormatFloat(float value) =>
+ $"{value:R}/0x{BitConverter.SingleToUInt32Bits(value):X8}";
+
+ private static string FormatVector(Vector3 value) =>
+ $"({FormatFloat(value.X)},{FormatFloat(value.Y)},{FormatFloat(value.Z)})";
+
+ private static string FormatQuaternion(Quaternion value) =>
+ $"({FormatFloat(value.X)},{FormatFloat(value.Y)},{FormatFloat(value.Z)},{FormatFloat(value.W)})";
+
+ private static string FormatMatrix(Matrix4x4 value) =>
+ string.Join(',', new[]
+ {
+ value.M11, value.M12, value.M13, value.M14,
+ value.M21, value.M22, value.M23, value.M24,
+ value.M31, value.M32, value.M33, value.M34,
+ value.M41, value.M42, value.M43, value.M44,
+ }.Select(FormatFloat));
+
+ private static ProductFacts AssertSemanticFacts(
+ LandblockBuild build,
+ EnvCellLandblockBuild envBuild,
+ PhysicsEngine engine)
+ {
+ EnvCellShellPlacement shell = Assert.Single(
+ envBuild.Shells,
+ candidate => candidate.CellId == ShellCellId);
+ Assert.NotEqual(0ul, shell.GeometryId);
+
+ WorldEntity ramp = Assert.Single(
+ build.Landblock.Entities,
+ entity => !entity.IsBuildingShell
+ && entity.SourceGfxObjOrSetupId == RampSetupId);
+ Assert.Equal(7, ramp.MeshRefs.Count);
+ Assert.True(engine.ShadowObjects.HasLogicalOwner(ramp.Id));
+ Assert.True(engine.ShadowObjects.TryGetRetailCellArray(ramp.Id, out var rampCells));
+ Assert.Equal([0xF4180112u, 0xF4180113u, 0xF4180009u], rampCells);
+ Assert.Equal(
+ RetailCellArrayRoute.BoundingBox,
+ engine.ShadowObjects.GetRetailCellArrayRoute(ramp.Id));
+
+ var rampEntries = new List();
+ foreach (uint cellId in rampCells)
+ {
+ RetailPartEntry[] entries = engine.ShadowObjects
+ .GetRetailPartEntriesInCell(cellId)
+ .Where(entry => entry.EntityId == ramp.Id)
+ .ToArray();
+ Assert.Equal(7, entries.Length);
+ Assert.Equal(Enumerable.Range(0, 7), entries.Select(entry => entry.PartIndex));
+ Assert.All(entries, entry =>
+ {
+ Assert.Equal(cellId, entry.CellId);
+ Assert.True(entry.ClipPlanesRequired);
+ });
+ rampEntries.AddRange(entries);
+ }
+ Assert.Equal(21, rampEntries.Count);
+
+ HashSet ownerIds = build.Landblock.Entities
+ .Select(entity => entity.Id)
+ .ToHashSet();
+ var cellDomain = new HashSet(
+ envBuild.VisibilityCells.Select(cell => cell.CellId));
+ foreach (WorldEntity entity in build.Landblock.Entities)
+ {
+ if (engine.ShadowObjects.TryGetRetailCellArray(entity.Id, out var cells))
+ cellDomain.UnionWith(cells);
+ }
+
+ uint crossOwnerCell = 0u;
+ foreach (uint cellId in cellDomain.Order())
+ {
+ IReadOnlyList entries =
+ engine.ShadowObjects.GetRetailPartEntriesInCell(cellId);
+ foreach (RetailPartEntry entry in entries)
+ {
+ Assert.Contains(entry.EntityId, ownerIds);
+ Assert.True(
+ engine.ShadowObjects.TryGetRetailCellArray(entry.EntityId, out var ownerCells));
+ Assert.Contains(cellId, ownerCells);
+ }
+ if (entries.Select(entry => entry.EntityId).Distinct().Skip(1).Any())
+ crossOwnerCell = cellId;
+ }
+ Assert.NotEqual(0u, crossOwnerCell);
+
+ Assert.Contains(build.Landblock.Entities, entity =>
+ engine.ShadowObjects.TryGetRetailCellArray(entity.Id, out var cells)
+ && cells.Count > 1);
+ Assert.Contains(build.Landblock.Entities, entity =>
+ engine.ShadowObjects.GetRetailCellArrayRoute(entity.Id)
+ == RetailCellArrayRoute.BoundingBox);
+
+ return new ProductFacts(ramp.Id, shell.GeometryId, crossOwnerCell);
+ }
+
+ private static (byte[] Bytes, GeometryCounts Counts) SerializeGeometry(
+ LandblockBuild build,
+ EnvCellLandblockBuild envBuild,
+ IPreparedAssetSource meshSource)
+ {
+ using var stream = new MemoryStream();
+ using var writer = new BinaryWriter(stream);
+ WriteVersion(writer, "ACDREAM-C1A-GEOMETRY-V1");
+ writer.Write(build.LandblockId);
+ WriteFloat(writer, envBuild.WalkMaxZ);
+ WriteFloat(writer, envBuild.WalkMinZ);
+
+ int vertexCount = 0;
+ int subsetCount = 0;
+ int indexCount = 0;
+ int setupPartCount = 0;
+
+ writer.Write(envBuild.VisibilityCells.Length);
+ foreach (LoadedCell cell in envBuild.VisibilityCells)
+ WriteVisibilityCell(writer, cell);
+
+ writer.Write(envBuild.Shells.Length);
+ foreach (EnvCellShellPlacement shell in envBuild.Shells)
+ WriteShellPlacement(writer, shell);
+
+ writer.Write(build.Landblock.Entities.Count);
+ foreach (WorldEntity entity in build.Landblock.Entities)
+ WritePublishedEntity(writer, entity);
+
+ writer.Write(envBuild.Shells.Length);
+ foreach (EnvCellShellPlacement shell in envBuild.Shells)
+ {
+ writer.Write(shell.CellId);
+ writer.Write(shell.GeometryId);
+ PreparedAssetReadResult read = meshSource.Read(
+ PreparedAssetRequest.EnvCellGeometry(
+ shell.CellId,
+ shell.GeometryId,
+ shell.EnvironmentId,
+ shell.CellStructure,
+ shell.Surfaces));
+ ObjectMeshData mesh = RequireMesh(read, "EnvCell", shell.CellId);
+ if (shell.CellId == ShellCellId)
+ {
+ // Establish real shell geometry before the first golden capture.
+ Assert.NotEmpty(mesh.Vertices);
+ var shellBatches = ObjectMeshManager.OrderedUploadBatches(mesh);
+ Assert.NotEmpty(shellBatches);
+ Assert.True(shellBatches.Sum(subset => subset.Batch.Indices.Count) > 0);
+ }
+ WriteMeshProduct(
+ writer,
+ mesh,
+ cellShell: true,
+ ref vertexCount,
+ ref subsetCount,
+ ref indexCount,
+ ref setupPartCount);
+ }
+
+ uint[] setupIds = build.Landblock.Entities
+ .Select(entity => entity.SourceGfxObjOrSetupId)
+ .Where(id => (id & 0xFF000000u) == 0x02000000u)
+ .Distinct()
+ .ToArray();
+ writer.Write(setupIds.Length);
+ foreach (uint setupId in setupIds)
+ {
+ writer.Write(setupId);
+ ObjectMeshData mesh = RequireMesh(
+ meshSource.Read(PreparedAssetRequest.Setup(setupId)),
+ "Setup",
+ setupId);
+ WriteMeshProduct(
+ writer,
+ mesh,
+ cellShell: false,
+ ref vertexCount,
+ ref subsetCount,
+ ref indexCount,
+ ref setupPartCount);
+ }
+
+ uint[] gfxObjIds = build.Landblock.Entities
+ .SelectMany(entity => entity.MeshRefs)
+ .Select(mesh => mesh.GfxObjId)
+ .Distinct()
+ .ToArray();
+ writer.Write(gfxObjIds.Length);
+ foreach (uint gfxObjId in gfxObjIds)
+ {
+ writer.Write(gfxObjId);
+ ObjectMeshData mesh = RequireMesh(
+ meshSource.Read(PreparedAssetRequest.GfxObj(gfxObjId)),
+ "GfxObj",
+ gfxObjId);
+ WriteMeshProduct(
+ writer,
+ mesh,
+ cellShell: false,
+ ref vertexCount,
+ ref subsetCount,
+ ref indexCount,
+ ref setupPartCount);
+ }
+
+ return (
+ stream.ToArray(),
+ new GeometryCounts(
+ envBuild.VisibilityCells.Length,
+ envBuild.Shells.Length,
+ build.Landblock.Entities.Count,
+ setupIds.Length,
+ gfxObjIds.Length,
+ vertexCount,
+ subsetCount,
+ indexCount,
+ setupPartCount));
+ }
+
+ private static (byte[] Bytes, MembershipCounts Counts) SerializeMembership(
+ LandblockBuild build,
+ EnvCellLandblockBuild envBuild,
+ PhysicsEngine engine,
+ ProductFacts facts)
+ {
+ using var stream = new MemoryStream();
+ using var writer = new BinaryWriter(stream);
+ WriteVersion(writer, "ACDREAM-C1A-MEMBERSHIP-V1");
+ writer.Write(build.LandblockId);
+ writer.Write(facts.RampEntityId);
+ writer.Write(facts.ShellGeometryId);
+ writer.Write(facts.CrossOwnerCellId);
+
+ int logicalOwnerCount = 0;
+ int retainedArrayCount = 0;
+ int retainedCellCount = 0;
+ var cellDomain = new HashSet(
+ envBuild.VisibilityCells.Select(cell => cell.CellId));
+
+ writer.Write(build.Landblock.Entities.Count);
+ foreach (WorldEntity entity in build.Landblock.Entities)
+ {
+ writer.Write(entity.Id);
+ bool hasLogicalOwner = engine.ShadowObjects.HasLogicalOwner(entity.Id);
+ writer.Write(hasLogicalOwner);
+ if (hasLogicalOwner)
+ logicalOwnerCount++;
+
+ bool hasRetailCellArray = engine.ShadowObjects.TryGetRetailCellArray(
+ entity.Id,
+ out IReadOnlyList cells);
+ writer.Write(hasRetailCellArray);
+ writer.Write((int)engine.ShadowObjects.GetRetailCellArrayRoute(entity.Id));
+ writer.Write(cells.Count);
+ foreach (uint cellId in cells)
+ {
+ writer.Write(cellId);
+ cellDomain.Add(cellId);
+ }
+ if (hasRetailCellArray)
+ {
+ retainedArrayCount++;
+ retainedCellCount += cells.Count;
+ }
+ }
+
+ uint[] orderedCellDomain = cellDomain.Order().ToArray();
+ int entryCount = 0;
+ writer.Write(orderedCellDomain.Length);
+ foreach (uint cellId in orderedCellDomain)
+ {
+ writer.Write(cellId);
+ IReadOnlyList entries =
+ engine.ShadowObjects.GetRetailPartEntriesInCell(cellId);
+ writer.Write(entries.Count);
+ foreach (RetailPartEntry entry in entries)
+ {
+ writer.Write(entry.EntityId);
+ writer.Write(entry.PartIndex);
+ writer.Write(entry.GfxObjId);
+ writer.Write(entry.CellId);
+ writer.Write(entry.ClipPlanesRequired);
+ entryCount++;
+ }
+ }
+
+ return (
+ stream.ToArray(),
+ new MembershipCounts(
+ build.Landblock.Entities.Count,
+ logicalOwnerCount,
+ retainedArrayCount,
+ retainedCellCount,
+ orderedCellDomain.Length,
+ entryCount));
+ }
+
+ private static byte[] SerializeCombined(byte[] geometry, byte[] membership)
+ {
+ using var stream = new MemoryStream();
+ using var writer = new BinaryWriter(stream);
+ WriteVersion(writer, "ACDREAM-C1A-COMBINED-V1");
+ writer.Write(geometry.Length);
+ writer.Write(geometry);
+ writer.Write(membership.Length);
+ writer.Write(membership);
+ return stream.ToArray();
+ }
+
+ private static void WriteVisibilityCell(BinaryWriter writer, LoadedCell cell)
+ {
+ writer.Write(cell.CellId);
+ WriteVector3(writer, cell.WorldPosition);
+ WriteMatrix(writer, cell.WorldTransform);
+ WriteMatrix(writer, cell.InverseWorldTransform);
+ WriteVector3(writer, cell.LocalBoundsMin);
+ WriteVector3(writer, cell.LocalBoundsMax);
+ WriteNullableUInt32(writer, cell.BuildingId);
+
+ writer.Write(cell.Portals.Count);
+ foreach (CellPortalInfo portal in cell.Portals)
+ {
+ writer.Write(portal.OtherCellId);
+ writer.Write(portal.PolygonId);
+ writer.Write(portal.Flags);
+ writer.Write(portal.OtherPortalId);
+ }
+ writer.Write(cell.ClipPlanes.Count);
+ foreach (PortalClipPlane plane in cell.ClipPlanes)
+ {
+ WriteVector3(writer, plane.Normal);
+ WriteFloat(writer, plane.D);
+ writer.Write(plane.InsideSide);
+ }
+ writer.Write(cell.PortalPolygons.Count);
+ foreach (Vector3[] polygon in cell.PortalPolygons)
+ {
+ writer.Write(polygon.Length);
+ foreach (Vector3 vertex in polygon)
+ WriteVector3(writer, vertex);
+ }
+ writer.Write(cell.VisibleCells.Count);
+ foreach (uint visibleCell in cell.VisibleCells)
+ writer.Write(visibleCell);
+ writer.Write(cell.SeenOutside);
+ writer.Write(cell.IsOutdoorNode);
+
+ writer.Write(cell.Walk is not null);
+ if (cell.Walk is not { } walk)
+ return;
+ writer.Write(walk.CellId);
+ WriteMatrix(writer, walk.WorldTransform);
+ WriteMatrix(writer, walk.InverseWorldTransform);
+ writer.Write(walk.Portals.Length);
+ foreach (WalkCellPortal portal in walk.Portals)
+ {
+ writer.Write(portal.OtherCellId);
+ writer.Write(portal.PolygonIndex);
+ writer.Write(portal.PortalSide);
+ writer.Write(portal.OtherPortalId);
+ writer.Write(portal.ExactMatch);
+ }
+ writer.Write(walk.PortalPolygons.Length);
+ foreach (WalkPolygon polygon in walk.PortalPolygons)
+ {
+ writer.Write(polygon.Vertices.Length);
+ foreach (Vector3 vertex in polygon.Vertices)
+ WriteVector3(writer, vertex);
+ WriteVector3(writer, polygon.Plane.Normal);
+ WriteFloat(writer, polygon.Plane.D);
+ }
+ writer.Write(walk.StabList.Length);
+ foreach (uint cellId in walk.StabList)
+ writer.Write(cellId);
+ }
+
+ private static void WriteShellPlacement(BinaryWriter writer, EnvCellShellPlacement shell)
+ {
+ writer.Write(shell.CellId);
+ writer.Write(shell.GeometryId);
+ writer.Write(shell.EnvironmentId);
+ writer.Write(shell.CellStructure);
+ writer.Write(shell.Surfaces.Length);
+ foreach (ushort surface in shell.Surfaces)
+ writer.Write(surface);
+ WriteVector3(writer, shell.WorldPosition);
+ WriteQuaternion(writer, shell.Rotation);
+ WriteMatrix(writer, shell.Transform);
+ WriteVector3(writer, shell.LocalBounds.Min);
+ WriteVector3(writer, shell.LocalBounds.Max);
+ WriteVector3(writer, shell.WorldBounds.Min);
+ WriteVector3(writer, shell.WorldBounds.Max);
+ }
+
+ private static void WritePublishedEntity(BinaryWriter writer, WorldEntity entity)
+ {
+ writer.Write(entity.Id);
+ writer.Write(entity.SourceGfxObjOrSetupId);
+ WriteVector3(writer, entity.Position);
+ WriteQuaternion(writer, entity.Rotation);
+ WriteFloat(writer, entity.Scale);
+ WriteNullableUInt32(writer, entity.ParentCellId);
+ WriteNullableUInt32(writer, entity.EffectCellId);
+ writer.Write(entity.IsBuildingShell);
+ WriteNullableUInt32(writer, entity.BuildingShellAnchorCellId);
+ writer.Write(entity.MeshRefs.Count);
+ foreach (MeshRef meshRef in entity.MeshRefs)
+ {
+ writer.Write(meshRef.GfxObjId);
+ WriteMatrix(writer, meshRef.PartTransform);
+ }
+ }
+
+ private static void WriteMeshProduct(
+ BinaryWriter writer,
+ ObjectMeshData mesh,
+ bool cellShell,
+ ref int vertexCount,
+ ref int subsetCount,
+ ref int indexCount,
+ ref int setupPartCount)
+ {
+ writer.Write(mesh.ObjectId);
+ writer.Write(mesh.IsSetup);
+ writer.Write(mesh.Vertices.Length);
+ foreach (VertexPositionNormalTexture vertex in mesh.Vertices)
+ {
+ WriteVector3(writer, vertex.Position);
+ WriteVector3(writer, vertex.Normal);
+ WriteVector2(writer, vertex.UV);
+ }
+ vertexCount += mesh.Vertices.Length;
+
+ writer.Write(mesh.Batches.Count);
+ foreach (MeshBatchData batch in mesh.Batches)
+ {
+ writer.Write(batch.Indices.Length);
+ foreach (ushort index in batch.Indices)
+ writer.Write(index);
+ writer.Write(batch.TextureFormat.Width);
+ writer.Write(batch.TextureFormat.Height);
+ writer.Write((int)batch.TextureFormat.Format);
+ WriteTextureKey(writer, batch.TextureKey);
+ writer.Write(batch.TextureIndex);
+ WriteNullableEnum(writer, batch.UploadPixelFormat);
+ WriteNullableEnum(writer, batch.UploadPixelType);
+ writer.Write((int)batch.CullMode);
+ indexCount += batch.Indices.Length;
+ }
+
+ List<((int Width, int Height, TextureFormat Format) Format, TextureBatchData Batch)> subsets =
+ cellShell
+ ? ObjectMeshManager.OrderedUploadBatches(mesh)
+ : mesh.TextureBatches
+ .OrderBy(pair => pair.Key.Width)
+ .ThenBy(pair => pair.Key.Height)
+ .ThenBy(pair => (int)pair.Key.Format)
+ .SelectMany(pair => pair.Value.Select(batch => (pair.Key, batch)))
+ .ToList();
+ writer.Write(subsets.Count);
+ foreach (var (format, batch) in subsets)
+ {
+ writer.Write(format.Width);
+ writer.Write(format.Height);
+ writer.Write((int)format.Format);
+ WriteTextureKey(writer, batch.Key);
+ WriteNullableEnum(writer, batch.UploadPixelFormat);
+ WriteNullableEnum(writer, batch.UploadPixelType);
+ writer.Write((int)batch.CullMode);
+ writer.Write((int)batch.Translucency);
+ writer.Write(batch.IsTransparent);
+ writer.Write(batch.IsAdditive);
+ writer.Write(batch.HasWrappingUVs);
+ WriteFloat(writer, batch.SurfaceOpacity);
+ writer.Write(batch.MaterialState.ToPackedByte());
+ writer.Write(batch.SourceSurfaceIndex);
+ writer.Write(batch.RetailSurfaceMask);
+ writer.Write(batch.RawSurfaceType);
+ writer.Write(batch.IsCellShell);
+ writer.Write(batch.Indices.Count);
+ foreach (ushort index in batch.Indices)
+ writer.Write(index);
+ subsetCount++;
+ indexCount += batch.Indices.Count;
+ }
+
+ writer.Write(mesh.SetupParts.Count);
+ foreach ((ulong gfxObjId, Matrix4x4 transform) in mesh.SetupParts)
+ {
+ writer.Write(gfxObjId);
+ WriteMatrix(writer, transform);
+ }
+ setupPartCount += mesh.SetupParts.Count;
+
+ WriteVector3(writer, mesh.BoundingBox.Min);
+ WriteVector3(writer, mesh.BoundingBox.Max);
+ WriteVector3(writer, mesh.SortCenter);
+ writer.Write(mesh.DIDDegrade);
+ writer.Write(mesh.SelectionSphere is not null);
+ if (mesh.SelectionSphere is { } sphere)
+ {
+ WriteVector3(writer, sphere.Origin);
+ WriteFloat(writer, sphere.Radius);
+ }
+ writer.Write(mesh.EdgeLines.Length);
+ foreach (Vector3 edgeVertex in mesh.EdgeLines)
+ WriteVector3(writer, edgeVertex);
+ }
+
+ private static ObjectMeshData RequireMesh(
+ PreparedAssetReadResult result,
+ string kind,
+ uint sourceId)
+ {
+ Assert.Equal(PreparedAssetReadStatus.Loaded, result.Status);
+ return Assert.IsType(result.Data);
+ }
+
+ private static Vector3 PublicationOrigin(LandblockBuildOrigin origin) => new(
+ (((int)((LandblockId >> 24) & 0xFFu)) - origin.CenterX) * 192f,
+ (((int)((LandblockId >> 16) & 0xFFu)) - origin.CenterY) * 192f,
+ 0f);
+
+ private static string Hash(byte[] bytes) =>
+ Convert.ToHexString(SHA256.HashData(bytes));
+
+ private static void WriteVersion(BinaryWriter writer, string value)
+ {
+ byte[] bytes = System.Text.Encoding.ASCII.GetBytes(value);
+ writer.Write(bytes.Length);
+ writer.Write(bytes);
+ }
+
+ private static void WriteNullableUInt32(BinaryWriter writer, uint? value)
+ {
+ writer.Write(value.HasValue);
+ if (value.HasValue)
+ writer.Write(value.Value);
+ }
+
+ private static void WriteTextureKey(BinaryWriter writer, TextureKey key)
+ {
+ writer.Write(key.SurfaceId);
+ writer.Write(key.PaletteId);
+ writer.Write((int)key.Stippling);
+ writer.Write(key.IsSolid);
+ }
+
+ private static void WriteNullableEnum(BinaryWriter writer, T? value)
+ where T : struct, Enum
+ {
+ writer.Write(value.HasValue);
+ if (value.HasValue)
+ writer.Write(Convert.ToInt32(value.Value));
+ }
+
+ private static void WriteVector2(BinaryWriter writer, Vector2 value)
+ {
+ WriteFloat(writer, value.X);
+ WriteFloat(writer, value.Y);
+ }
+
+ private static void WriteVector3(BinaryWriter writer, Vector3 value)
+ {
+ WriteFloat(writer, value.X);
+ WriteFloat(writer, value.Y);
+ WriteFloat(writer, value.Z);
+ }
+
+ private static void WriteQuaternion(BinaryWriter writer, Quaternion value)
+ {
+ WriteFloat(writer, value.X);
+ WriteFloat(writer, value.Y);
+ WriteFloat(writer, value.Z);
+ WriteFloat(writer, value.W);
+ }
+
+ private static void WriteMatrix(BinaryWriter writer, Matrix4x4 value)
+ {
+ WriteFloat(writer, value.M11);
+ WriteFloat(writer, value.M12);
+ WriteFloat(writer, value.M13);
+ WriteFloat(writer, value.M14);
+ WriteFloat(writer, value.M21);
+ WriteFloat(writer, value.M22);
+ WriteFloat(writer, value.M23);
+ WriteFloat(writer, value.M24);
+ WriteFloat(writer, value.M31);
+ WriteFloat(writer, value.M32);
+ WriteFloat(writer, value.M33);
+ WriteFloat(writer, value.M34);
+ WriteFloat(writer, value.M41);
+ WriteFloat(writer, value.M42);
+ WriteFloat(writer, value.M43);
+ WriteFloat(writer, value.M44);
+ }
+
+ private static void WriteFloat(BinaryWriter writer, float value) =>
+ writer.Write(BitConverter.SingleToInt32Bits(value));
+
+ private readonly record struct Capture(
+ byte[] GeometryBytes,
+ byte[] MembershipBytes,
+ string GeometryHash,
+ string MembershipHash,
+ string CombinedHash,
+ ProductCounts Counts);
+
+ private readonly record struct ProductFacts(
+ uint RampEntityId,
+ ulong ShellGeometryId,
+ uint CrossOwnerCellId);
+
+ private readonly record struct GeometryCounts(
+ int VisibilityCells,
+ int Shells,
+ int PublishedEntities,
+ int SetupMeshes,
+ int GfxObjMeshes,
+ int Vertices,
+ int Subsets,
+ int Indices,
+ int SetupParts);
+
+ private readonly record struct MembershipCounts(
+ int PublishedEntities,
+ int LogicalOwners,
+ int RetainedCellArrays,
+ int RetainedCellArrayCells,
+ int CellDomain,
+ int PartEntries);
+
+ private readonly record struct ProductCounts(
+ GeometryCounts Geometry,
+ MembershipCounts Membership);
+
+ private enum RetailPlaneSide
+ {
+ Positive = 0,
+ Negative = 1,
+ InPlane = 2,
+ Crossing = 3,
+ }
+
+ private readonly record struct RetailBoxClassification(
+ RetailPlaneSide Side,
+ IReadOnlyList CornerSides,
+ double EpsilonTieMargin);
+
+ ///
+ /// Test-only installed-DAT implementation of the immutable prepared
+ /// collision source. It performs only the existing flat conversions;
+ /// membership remains exclusively owned by PublishStaticCollision and
+ /// ShadowObjectRegistry.
+ ///
+ private sealed class InstalledDatPreparedCollisionAdapter : IPreparedCollisionSource
+ {
+ private readonly IDatReaderWriter _dats;
+ private long _probes;
+ private long _reads;
+ private long _loaded;
+ private long _missing;
+
+ public InstalledDatPreparedCollisionAdapter(IDatReaderWriter dats) =>
+ _dats = dats;
+
+ public PreparedCollisionSourceStats CollisionStats => new(
+ _probes,
+ _reads,
+ _loaded,
+ _missing,
+ Corrupt: 0);
+
+ public PreparedAssetPresence ProbeCollision(PakAssetType type, uint sourceFileId)
+ {
+ _probes++;
+ bool available = type switch
+ {
+ PakAssetType.GfxObjCollision => _dats.Get(sourceFileId) is not null,
+ PakAssetType.SetupCollision => _dats.Get(sourceFileId) is not null,
+ PakAssetType.CellStructureCollision =>
+ TryResolveCell(sourceFileId, out _, out _),
+ PakAssetType.EnvCellTopology =>
+ TryResolveCell(sourceFileId, out _, out _),
+ _ => throw new ArgumentOutOfRangeException(nameof(type)),
+ };
+ return available
+ ? PreparedAssetPresence.Available
+ : PreparedAssetPresence.Missing;
+ }
+
+ public PreparedCollisionReadResult ReadGfxObjCollision(
+ uint sourceFileId,
+ CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ _reads++;
+ DatGfxObj? value = _dats.Get(sourceFileId);
+ if (value is null)
+ return Missing();
+ return Loaded(FlatCollisionAssetBuilder.FlattenGfxObj(value));
+ }
+
+ public PreparedCollisionReadResult ReadSetupCollision(
+ uint sourceFileId,
+ CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ _reads++;
+ DatSetup? value = _dats.Get(sourceFileId);
+ if (value is null)
+ return Missing();
+ return Loaded(FlatCollisionAssetBuilder.FlattenSetup(value));
+ }
+
+ public PreparedCollisionReadResult
+ ReadCellStructureCollision(
+ uint sourceFileId,
+ CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ _reads++;
+ if (!TryResolveCell(sourceFileId, out _, out var structure))
+ return Missing();
+ return Loaded(FlatCollisionAssetBuilder.FlattenCellStructure(structure));
+ }
+
+ public PreparedCollisionReadResult ReadEnvCellTopology(
+ uint sourceFileId,
+ CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ _reads++;
+ if (!TryResolveCell(sourceFileId, out DatEnvCell? cell, out var structure))
+ return Missing();
+ FlatCellStructureCollisionAsset flat =
+ FlatCollisionAssetBuilder.FlattenCellStructure(structure);
+ return Loaded(FlatCollisionAssetBuilder.FlattenEnvCellTopology(
+ sourceFileId,
+ cell!,
+ flat.PortalPolygons));
+ }
+
+ private bool TryResolveCell(
+ uint sourceFileId,
+ out DatEnvCell? cell,
+ out DatReaderWriter.Types.CellStruct structure)
+ {
+ cell = _dats.Get(sourceFileId);
+ if (cell is not null
+ && _dats.Get(0x0D000000u | cell.EnvironmentId) is { } environment
+ && environment.Cells.TryGetValue(cell.CellStructure, out var found)
+ && found is not null)
+ {
+ structure = found;
+ return true;
+ }
+ structure = null!;
+ return false;
+ }
+
+ private PreparedCollisionReadResult Loaded(T value)
+ where T : class
+ {
+ _loaded++;
+ return PreparedCollisionReadResult.Loaded(value);
+ }
+
+ private PreparedCollisionReadResult Missing()
+ where T : class
+ {
+ _missing++;
+ return PreparedCollisionReadResult.Missing;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}