using System; using System.IO; using System.Numerics; using AcDream.Core.Physics; using DatReaderWriter; using DatReaderWriter.Options; using Xunit; using Xunit.Abstractions; namespace AcDream.Core.Tests.Conformance; /// /// P1 decisive evidence — the PRODUCTION-PATH membership conformance. Replays the /// captured retail doorway golden through the REAL /// (which builds the global sphere and SWEEPS it), then checks whether the swept /// CellId matches retail's committed cell. This is the integration test the /// P0 bare-FindCellList probe could NOT be: the probe fed the resting foot /// origin with no sweep, so it always reported the cell the foot stood in. Here the /// engine sweeps prev→curr, so the sphere crosses the doorway exactly as retail's did. /// /// Diagnostic-first: prints MATCH/DIVERGE per segment so we read the real production /// behaviour before pinning an assertion. Outcomes (notes /// docs/research/2026-06-03-p0-conformance-apparatus-notes.md §CORRECTION): production /// MATCHES → the P0 divergence was a probe artifact; DIVERGES → a real bug to design from. /// /// Scope here = the INDOOR segments (vestibule 0170 ↔ room 0171), which need only the /// building-cell cache. The outdoor-involving segments (0031↔0170) need the landcell + /// building portal and are a follow-up. /// public class ThresholdPortalCrossingReplayTests { private readonly ITestOutputHelper _out; public ThresholdPortalCrossingReplayTests(ITestOutputHelper output) => _out = output; // The doorway floor is flat at the captured pz across all transitions. private const float FloorZ = 94.005f; private const float SphereRadius = 0.4f; private const float SphereHeight = 1.2f; private const float StepUpHeight = 0.4f; private const float StepDownHeight = 0.1f; private static (PhysicsEngine, PhysicsDataCache) BuildBuildingEngine(DatCollection dats) { var cache = new PhysicsDataCache(); var engine = new PhysicsEngine { DataCache = cache }; for (uint low = 0x016Fu; low <= 0x0175u; low++) ConformanceDats.LoadEnvCell(dats, cache, ConformanceDats.HoltburgLandblock | low); // Stub landblock for FindObjCollisions context (flat far-below terrain; the // indoor BSP path fires first, terrain is never consulted). Matches the // CellarUpTrajectoryReplay harness pattern. var heights = new byte[81]; var heightTable = new float[256]; for (int i = 0; i < 256; i++) heightTable[i] = -1000f; engine.AddLandblock(0xA9B40000u, new TerrainSurface(heights, heightTable), Array.Empty(), Array.Empty(), 0f, 0f); return (engine, cache); } private static PhysicsBody GroundedBodyAt(Vector3 pos, uint cellId) => new() { Position = pos, Orientation = Quaternion.Identity, ContactPlaneValid = true, ContactPlane = new Plane(0f, 0f, 1f, -FloorZ), ContactPlaneCellId = cellId, WalkablePolygonValid = true, WalkablePlane = new Plane(0f, 0f, 1f, -FloorZ), WalkableVertices = new[] { new Vector3(150f, 5f, FloorZ), new Vector3(150f, 20f, FloorZ), new Vector3(165f, 20f, FloorZ), new Vector3(165f, 5f, FloorZ), }, WalkableUp = Vector3.UnitZ, TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable, }; private static uint Low(uint id) => id & 0xFFFFu; /// /// Documents-the-bug (GREEN while acdream diverges; FAILS when P1 lands → rewrite to /// per-segment Assert.Equal). The swept production path DIVERGES from retail on the /// indoor doorway crossings: ResolveWithTransition completes the move /// (restPos == target) but leaves CellId on the SOURCE cell — it never /// advances curr_cell across the portal the way retail's change_cell golden /// does. So the P0 finding is NOT a probe artifact: production membership genuinely lags. /// P1 must port retail's swept-crossing curr_cell advance (how the sphere crossing /// the doorway polygon / the leading sphere point promotes the neighbour to the membership /// answer mid-sweep), then this flips to all-match. /// [Fact] public void ProductionPath_IndoorCrossings_DivergeFromRetail_PendingP1() { var datDir = ConformanceDats.ResolveDatDir(); if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; } var fixturePath = Path.Combine(ConformanceDats.FixturesDir, "find-cell-list-threshold.log"); if (!File.Exists(fixturePath)) { _out.WriteLine("SKIP: capture pending"); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); var (engine, _) = BuildBuildingEngine(dats); var picks = RetailTrace.ParseAll(File.ReadAllLines(fixturePath)); int match = 0, total = 0; for (int i = 0; i + 1 < picks.Count; i++) { uint fromCell = picks[i].PickedCellId; uint toCell = picks[i + 1].PickedCellId; if (Low(fromCell) < 0x100 || Low(toCell) < 0x100) continue; // indoor segments only var body = GroundedBodyAt(picks[i].Position, fromCell); var result = engine.ResolveWithTransition( currentPos: picks[i].Position, targetPos: picks[i + 1].Position, cellId: fromCell, sphereRadius: SphereRadius, sphereHeight: SphereHeight, stepUpHeight: StepUpHeight, stepDownHeight: StepDownHeight, isOnGround: true, body: body, moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, movingEntityId: 0); bool ok = result.CellId == toCell; if (ok) match++; total++; _out.WriteLine( $"seg 0x{Low(fromCell):X4}->0x{Low(toCell):X4} " + $"pos=({picks[i].Position.X:F2},{picks[i].Position.Y:F2})->({picks[i + 1].Position.X:F2},{picks[i + 1].Position.Y:F2}) " + $"acdream=0x{Low(result.CellId):X4} restPos=({result.Position.X:F2},{result.Position.Y:F2},{result.Position.Z:F2}) " + $"{(ok ? "MATCH" : "DIVERGE")}"); } _out.WriteLine($"=== production-path indoor crossings: {match}/{total} match retail ==="); Assert.True(total > 0, "no indoor doorway segments found in the golden"); Assert.True(match < total, $"acdream's swept ResolveWithTransition now reproduces {match}/{total} retail indoor " + "doorway crossings. If match == total, P1's curr_cell swept-advance port landed -> " + "rewrite this to Assert.Equal(toCell, result.CellId) per segment. (Retail truth = the golden.)"); } }