105 lines
4.4 KiB
C#
105 lines
4.4 KiB
C#
using AcDream.Core.Physics;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
using DatReaderWriter.Options;
|
|
|
|
namespace AcDream.Content.Tests;
|
|
|
|
/// <summary>
|
|
/// AP-22 reachability proof. The deleted Setup-radius collision fallback fired
|
|
/// only for a Setup with no CylSphere, no Sphere, and a nonzero summary
|
|
/// Radius. This sweeps every Setup in the installed client_portal.dat and
|
|
/// asserts no such Setup exists, so the deletion cannot remove collision from
|
|
/// any real object.
|
|
/// </summary>
|
|
[Trait("Lane", "InstalledDat")]
|
|
public sealed class InstalledSetupCollisionReachabilityTests
|
|
{
|
|
// Positive controls. These are EXTERNAL constants, measured on 2026-08-06
|
|
// by an independent raw client_portal.dat parse (B-tree directory walk +
|
|
// Setup record decode) that validated itself by byte accounting: all
|
|
// 5,935 records consumed with an exact residual tail of
|
|
// 20 + 48 * numLights and zero unexplained bytes.
|
|
//
|
|
// They are deliberately NOT derived from FlatCollisionAssetBuilder. A
|
|
// broken enumeration, a wrong dat path, or a silently-empty flatten all
|
|
// satisfy the negative claim vacuously and are caught only here.
|
|
private const int ExpectedSetups = 5935;
|
|
private const int ExpectedWithCylinder = 678;
|
|
private const int ExpectedSphereOnlyNoCylinder = 3605;
|
|
private const int ExpectedWithoutAnyPrimitive = 1652;
|
|
private const int ExpectedWithSummaryRadius = 4282;
|
|
|
|
[Fact]
|
|
public void InstalledSetups_NeverReachTheDeletedRadiusFallback()
|
|
{
|
|
string? datDir = ContentConformanceDats.ResolveDatDir();
|
|
if (datDir is null)
|
|
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
|
|
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
|
|
int total = 0;
|
|
int withCylinder = 0;
|
|
int sphereOnly = 0;
|
|
int withoutPrimitive = 0;
|
|
int withRadius = 0;
|
|
var fallbackReachable = new List<uint>();
|
|
var fallbackReachableWideGuard = new List<uint>();
|
|
|
|
foreach (uint id in dats.GetAllIdsOfType<Setup>())
|
|
{
|
|
if (!dats.Portal.TryGet<Setup>(id, out Setup? setup)
|
|
|| setup is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FlatSetupCollision flat =
|
|
FlatCollisionAssetBuilder.FlattenSetup(setup);
|
|
total++;
|
|
|
|
bool hasCylinder = flat.Cylinders.Length > 0;
|
|
bool hasSphere = flat.Spheres.Length > 0;
|
|
if (hasCylinder)
|
|
withCylinder++;
|
|
else if (hasSphere)
|
|
sphereOnly++;
|
|
else
|
|
withoutPrimitive++;
|
|
|
|
if (flat.Radius > 0.0001f)
|
|
withRadius++;
|
|
|
|
// BOTH guard variants the deleted copies used — they are not the
|
|
// same predicate. Site 1 (LiveEntityCollisionBuilder) tested
|
|
// `Radius > 0.0001f`; sites 2 and 3 (LandblockPhysicsPublisher,
|
|
// LandblockPhysicsContentBuilder — the headless-reachable one)
|
|
// tested the strictly wider `Radius > 0f`. Corrected 2026-08-06 at
|
|
// the AP-22 architecture review, which measured that the two
|
|
// genuinely differ over the installed DAT by exactly one Setup:
|
|
// 0x02001657, whose radius is the denormal 1.3e-39. Asserting only
|
|
// the narrow guard would have claimed coverage of two deletions it
|
|
// never evaluated.
|
|
if (!hasCylinder && !hasSphere && flat.Radius > 0.0001f)
|
|
fallbackReachable.Add(id);
|
|
if (!hasCylinder && !hasSphere && flat.Radius > 0f)
|
|
fallbackReachableWideGuard.Add(id);
|
|
}
|
|
|
|
// (b) Positive controls first: if the enumeration is broken, fail here
|
|
// rather than passing (a) for the wrong reason.
|
|
Assert.Equal(ExpectedSetups, total);
|
|
Assert.Equal(ExpectedWithCylinder, withCylinder);
|
|
Assert.Equal(ExpectedSphereOnlyNoCylinder, sphereOnly);
|
|
Assert.Equal(ExpectedWithoutAnyPrimitive, withoutPrimitive);
|
|
Assert.Equal(ExpectedWithSummaryRadius, withRadius);
|
|
|
|
// (a) The negative claim the deletion rests on.
|
|
Assert.Empty(fallbackReachable);
|
|
// The wider guard sites 2 and 3 actually used. Zero here is what makes
|
|
// the headless-reachable deletion safe; the narrow guard above does
|
|
// not evaluate it.
|
|
Assert.Empty(fallbackReachableWideGuard);
|
|
}
|
|
}
|