feat(content): bake and read flat collision assets
Append strict collision/topology payloads to the existing prepared package so later physics cutover can drop parsed DAT graphs without adding a second mapping or changing traversal behavior. The full 2,232,170-key catalog is deterministic across worker counts, exact-byte aliased, corruption-isolated, and cancellation-safe.
This commit is contained in:
parent
d9300c7854
commit
9cd42417a8
29 changed files with 2868 additions and 63 deletions
|
|
@ -76,6 +76,43 @@ public sealed class BakeDeterminismTests : IDisposable {
|
|||
$"pak sizes differ between runs: {bytesA.Length} vs {bytesB.Length} bytes");
|
||||
Assert.True(bytesA.AsSpan().SequenceEqual(bytesB),
|
||||
"two bakes of the same id set produced different bytes — the sorted-batching determinism guarantee is broken");
|
||||
using var reader = new PakReader(pathA);
|
||||
foreach (uint gfxId in new[]
|
||||
{
|
||||
0x01000001u,
|
||||
0x010002B4u,
|
||||
0x010008A8u,
|
||||
0x010014C3u,
|
||||
})
|
||||
{
|
||||
Assert.True(reader.ContainsKey(PakKey.Compose(
|
||||
PakAssetType.GfxObjCollision,
|
||||
gfxId)));
|
||||
}
|
||||
foreach (uint setupId in new[]
|
||||
{
|
||||
0x02000001u,
|
||||
0x020019FFu,
|
||||
0x020005D8u,
|
||||
})
|
||||
{
|
||||
Assert.True(reader.ContainsKey(PakKey.Compose(
|
||||
PakAssetType.SetupCollision,
|
||||
setupId)));
|
||||
}
|
||||
foreach (uint cellId in new[]
|
||||
{
|
||||
0xA9B40100u,
|
||||
0xA9B40101u,
|
||||
})
|
||||
{
|
||||
Assert.True(reader.ContainsKey(PakKey.Compose(
|
||||
PakAssetType.CellStructureCollision,
|
||||
cellId)));
|
||||
Assert.True(reader.ContainsKey(PakKey.Compose(
|
||||
PakAssetType.EnvCellTopology,
|
||||
cellId)));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -111,7 +148,11 @@ public sealed class BakeDeterminismTests : IDisposable {
|
|||
Assert.Equal(8, reportA.EnvCellKeys);
|
||||
Assert.Equal(1, reportA.UniqueEnvCellGeometries);
|
||||
Assert.Equal(7, reportA.EnvCellAliases);
|
||||
Assert.Equal(1, reportA.PhysicalBlobs);
|
||||
Assert.Equal(8, reportA.CellStructureCollisionKeys);
|
||||
Assert.Equal(1, reportA.UniqueCellStructureCollisions);
|
||||
Assert.Equal(7, reportA.CellStructureCollisionAliases);
|
||||
Assert.Equal(8, reportA.EnvCellTopologyKeys);
|
||||
Assert.Equal(10, reportA.PhysicalBlobs);
|
||||
Assert.Equal(reportA.TotalKeys, reportB.TotalKeys);
|
||||
Assert.True(File.ReadAllBytes(pathA).AsSpan().SequenceEqual(File.ReadAllBytes(pathB)));
|
||||
|
||||
|
|
@ -124,6 +165,27 @@ public sealed class BakeDeterminismTests : IDisposable {
|
|||
Assert.All(receipts, receipt => Assert.Equal(receipts[0].Length, receipt.Length));
|
||||
Assert.All(receipts, receipt => Assert.Equal(receipts[0].Crc32, receipt.Crc32));
|
||||
|
||||
var collisionReceipts = ids
|
||||
.Select(id => reader.GetTocEntryForTest(
|
||||
PakKey.Compose(PakAssetType.CellStructureCollision, id)))
|
||||
.ToArray();
|
||||
Assert.All(
|
||||
collisionReceipts,
|
||||
receipt => Assert.Equal(
|
||||
collisionReceipts[0].Offset,
|
||||
receipt.Offset));
|
||||
Assert.All(
|
||||
collisionReceipts,
|
||||
receipt => Assert.Equal(
|
||||
collisionReceipts[0].Length,
|
||||
receipt.Length));
|
||||
Assert.Equal(
|
||||
ids.Count,
|
||||
ids.Select(id => reader.GetTocEntryForTest(
|
||||
PakKey.Compose(PakAssetType.EnvCellTopology, id)).Offset)
|
||||
.Distinct()
|
||||
.Count());
|
||||
|
||||
Assert.True(
|
||||
reader.TryReadObjectMeshData(
|
||||
PakKey.Compose(PakAssetType.EnvCellMesh, ids.Min()),
|
||||
|
|
@ -132,4 +194,34 @@ public sealed class BakeDeterminismTests : IDisposable {
|
|||
Assert.Equal(0xE000_0000_0000_0000UL,
|
||||
geometry.ObjectId & 0xF000_0000_0000_0000UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bake_CancellationCannotReplaceLastGoodPackage()
|
||||
{
|
||||
string? datDir = ResolveDatDir();
|
||||
if (datDir is null)
|
||||
return;
|
||||
|
||||
string destination = NewTempPakPath();
|
||||
byte[] lastGood = [0x41, 0x43, 0x50, 0x4B, 1, 2, 3, 4];
|
||||
File.WriteAllBytes(destination, lastGood);
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
cancellation.CancelAfter(TimeSpan.FromMilliseconds(20));
|
||||
|
||||
Assert.Throws<OperationCanceledException>(
|
||||
() => BakeRunner.RunDetailed(
|
||||
new BakeOptions
|
||||
{
|
||||
DatDir = datDir,
|
||||
OutPath = destination,
|
||||
Threads = 2,
|
||||
CancellationToken = cancellation.Token,
|
||||
}));
|
||||
|
||||
Assert.Equal(lastGood, File.ReadAllBytes(destination));
|
||||
Assert.Empty(
|
||||
Directory.EnumerateFiles(
|
||||
Path.GetDirectoryName(destination)!,
|
||||
$".{Path.GetFileName(destination)}.*.tmp"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue