test(physics): P4 review - RestrictionObj prevalence inspection over the installed cell DAT

103,766 of 729,888 EnvCells (14%, 1,293 landblocks - the entire housing
estate, 0x70xxxxxx GUIDs) carry a baked RestrictionObj. The AP-71 gate
as wired (CanMoveInto unmodeled, fail-closed) would therefore lock every
housing interior for everyone; retail's CanMoveInto (0x0058da40) is
fail-OPEN for unowned houses and for a null RestrictionDB. Fix directed
back to the P4 implementer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 11:01:10 +02:00
parent ce941de5cb
commit 3b5e099241

View file

@ -0,0 +1,94 @@
using System.Collections.Generic;
using AcDream.Core.Tests.Conformance;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;
using Xunit.Abstractions;
namespace AcDream.Core.Tests.Physics;
/// <summary>
/// AP-129 inspection (Campaign P P4 Opus review, 2026-07-30): measures how
/// many EnvCells in the INSTALLED cell DAT actually carry a baked
/// <c>RestrictionObj</c>. Decides the blast radius of the AP-71
/// fail-closed gate: with <c>CanMoveInto</c> unmodeled (AP-129), every
/// restricted cell refuses entry for every non-bypass player — harmless
/// if the flag is rare/authored-for-housing-only, a live lockout if it is
/// broadly baked into ordinary interiors. Inspection only; skips cleanly
/// when the installed DATs are absent (CI), following the
/// DoorSetupGfxObjInspectionTests pattern.
/// </summary>
public sealed class RestrictionObjPrevalenceInspectionTests
{
private readonly ITestOutputHelper _output;
public RestrictionObjPrevalenceInspectionTests(ITestOutputHelper output)
=> _output = output;
[Fact]
public void CountRestrictionObjCells_InstalledCellDat()
{
string? datDir = ConformanceDats.ResolveDatDir();
if (datDir is null)
return; // installed dats absent (CI) — skip cleanly
using var dats = new DatCollection(datDir, DatAccessType.Read);
// Bake-tool enumeration shape (BakeRunner.EnumerateEnvCellIds):
// LandBlockInfo (xxxxFFFE) NumCells ranges name every EnvCell id.
var landblockInfoIds = new List<uint>();
foreach (var file in dats.Cell.Tree)
{
if ((file.Id & 0xFFFFu) == 0xFFFEu)
landblockInfoIds.Add(file.Id);
}
int totalCells = 0;
int restricted = 0;
var restrictedLandblocks = new HashSet<uint>();
var samples = new List<string>();
foreach (uint infoId in landblockInfoIds)
{
if (!dats.Cell.TryGet<DatReaderWriter.DBObjs.LandBlockInfo>(
infoId, out var info)
|| info is null
|| info.NumCells == 0)
{
continue;
}
uint firstCellId = (infoId & 0xFFFF_0000u) | 0x0100u;
for (uint offset = 0; offset < info.NumCells; offset++)
{
uint cellId = firstCellId + offset;
if (!dats.Cell.TryGet<DatReaderWriter.DBObjs.EnvCell>(
cellId, out var cell)
|| cell is null)
{
continue;
}
totalCells++;
if (cell.RestrictionObj != 0)
{
restricted++;
restrictedLandblocks.Add(cellId & 0xFFFF_0000u);
if (samples.Count < 40)
{
samples.Add(
$"cell 0x{cellId:X8} restrictionObj 0x{cell.RestrictionObj:X8}");
}
}
}
}
_output.WriteLine(
$"EnvCells scanned: {totalCells}; with RestrictionObj != 0: "
+ $"{restricted}; distinct landblocks: {restrictedLandblocks.Count}");
foreach (string s in samples)
_output.WriteLine(s);
Assert.True(totalCells > 0, "installed cell DAT enumerated no EnvCells");
}
}