using System.Collections.Generic; using System.Linq; using System.Numerics; using AcDream.Core.Physics; using AcDream.Core.World; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Options; using DatReaderWriter.Types; namespace AcDream.Content.Tests; /// /// #334, replayed against the installed DATs from the user's own live /// evidence (334-neftet-probe.log, 8,401 lines, 2026-08-06). /// /// /// Standing inside the Neftet rock formation the broadphase reported /// inCell=2 exempt=2 reached=0 — the formation was not in the player's /// cell at all. The one object that DID block, GfxObj 0x010046D8 /// (root bounding sphere radius 69.471 m, bounds centre 34.977 m off the part /// origin), was measured PRESENT in cells 0x8764000A and /// 0x87640012 and ABSENT from 0x87640011, 0x87640019 and /// 0x87630018. Those five observations have exactly one explanation: /// a 3×3 land-cell neighbourhood centred on 0x87640013 — the cell /// under the object's own position — which is what /// CellTransit.AddAllOutsideCells produces for ANY sphere, because its /// minRad = radius / maxRad = 24 - radius boundary tests are /// unconditionally true above 12 m and it only ever adds the eight /// neighbours. /// /// /// /// This test asserts the observed reality rather than a constant: the two /// cells the probe measured EMPTY must be occupied, and the two it measured /// POPULATED must stay occupied. It fails at f0588725 (the sphere route /// cannot reach cellY 0 from a centre at cellY 2) and passes with the box /// route. The expected rectangle was derived from the object's own /// CGfxObj::gfx_bound_box read out of client_portal.dat, not /// from running the code under test: the box is 96 m × 96 m about a part /// origin at block-local (63.78, 56.29), i.e. cell (2,2), so it spans cell /// columns 0..4 on both axes. /// /// public sealed class Issue334NeftetFormationCellMembershipTests { private const uint NeftetLandblock = 0x87640000u; private const uint NeftetLandblockInfo = 0x8764FFFEu; private const uint FormationGfxObj = 0x010046D8u; // The four cells named in the probe log, by their measured disposition. private const uint MeasuredPresentA = 0x8764000Au; // lcoord (1081, 801) private const uint MeasuredPresentB = 0x87640012u; // lcoord (1082, 801) private const uint MeasuredAbsentA = 0x87640011u; // lcoord (1082, 800) private const uint MeasuredAbsentB = 0x87640019u; // lcoord (1083, 800) [Fact] public void NeftetFormation_RegistersInTheCellsTheProbeMeasuredEmpty() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) return; using var dats = new DatCollection(datDir, DatAccessType.Read); Assert.True( dats.Cell.TryGet(NeftetLandblockInfo, out LandBlockInfo? info) && info is not null, "Neftet landblock info 0x8764FFFE is absent from the installed cell dat."); // The probe's entity 0xC8764000 is stab index 0 of this landblock // (LandblockStaticEntityIdAllocator's 0xCXXYYIII packing). Stab formation = info!.Objects.First(o => o.Id == FormationGfxObj); Assert.True( dats.Portal.TryGet(FormationGfxObj, out GfxObj? gfx) && gfx is not null, "GfxObj 0x010046D8 is absent from the installed portal dat."); // Control: the fixture must be non-degenerate on the axis under test. // A box that fits inside its own bounding sphere makes the box route // and the sphere route agree, and proves nothing. var cache = new PhysicsDataCache(); cache.CacheGfxObj(FormationGfxObj, gfx!); GfxObjPhysics? phys = cache.GetGfxObj(FormationGfxObj); Assert.NotNull(phys); Assert.NotNull(phys!.VisualBounds); FlatGfxObjVisualBounds box = phys.VisualBounds!.Value; float extentX = box.Max.X - box.Min.X; float extentY = box.Max.Y - box.Min.Y; float rootRadius = phys.BoundingSphere!.Radius; Assert.True( extentX > rootRadius && extentY > rootRadius, $"Fixture is degenerate: extent ({extentX:F2}, {extentY:F2}) does not " + $"exceed the root sphere radius {rootRadius:F3}."); Assert.True( extentX > 48f && extentY > 48f, $"Fixture cannot reach two cells away: extent ({extentX:F2}, {extentY:F2})."); IReadOnlyList shapes = ShadowShapeBuilder.FromLandblockBspParts( new[] { new MeshRef(FormationGfxObj, Matrix4x4.Identity) }, isBuildingShell: false, cache.GetGfxObj); ShadowShape only = Assert.Single(shapes); Assert.Equal(ShadowCollisionType.BSP, only.CollisionType); var registry = new ShadowObjectRegistry { DataCache = cache }; const uint ownerId = 0xC8764000u; registry.RegisterMultiPart( ownerId, formation.Frame.Origin, formation.Frame.Orientation, shapes, 0u, EntityCollisionFlags.None, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: NeftetLandblock, seedCellId: 0u, isStatic: true); var held = new List(); for (uint index = 1u; index <= 64u; index++) { uint cellId = NeftetLandblock | index; if (registry.GetObjectsInCell(cellId).Any(e => e.EntityId == ownerId)) held.Add(cellId); } // The measured-populated pair must stay populated. Assert.Contains(MeasuredPresentA, held); Assert.Contains(MeasuredPresentB, held); // The measured-EMPTY pair is the #334 fact. Assert.Contains(MeasuredAbsentA, held); Assert.Contains(MeasuredAbsentB, held); // And the rectangle is the 5×5 the 96 m box spans about cell (2,2), // clipped to this landblock's own 8×8 grid (the two columns below 0 // land in the neighbour blocks 0x8763 / 0x8664 and are counted there). Assert.Equal(25, held.Count); } }