using AcDream.Core.Physics; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Options; namespace AcDream.Content.Tests; /// /// AP-152 population + behaviour proof over the installed client_portal.dat. /// /// /// Retail dispatches a Setup's collision geometry EXCLUSIVELY, BSP first, at /// both consumers: CPhysicsObj::FindObjCollisions @0x0050f050 tests /// HAS_PHYSICS_BSP_PS at 0x0050f165 and leaves the BSP branch /// through the unconditional 0x0050f19d jmp 0x50f2b0, past both the /// CylSphere loop (0x50f1a2) and the Sphere loop (0x50f21d); and /// CPhysicsObj::calc_cross_cells @0x00515230 tests the same flag at /// 0x00515285 and routes to CPhysicsObj::find_bbox_cell_list /// @0x00510fc0 at 0x0051528f jne 0x515305, never reaching its /// cylsphere (0x005152d1) or sorting-sphere (0x005152fb) branches. /// /// /// /// This sweep pins the affected population and asserts that /// emits NO primitive for any of /// it. Retail derives the dispatch flag from the parts themselves /// (CPartArray::CacheHasPhysicsBSP @0x00518110 ORs 0x10000 on the first /// part whose gfxobj->physics_bsp is non-null), which is exactly the /// predicate used here. /// /// public sealed class InstalledSetupBspPrimitiveDispatchTests { // EXTERNAL constants. The four bucket controls are the ones already // committed by the AP-22 reachability sweep (measured by an independent // raw client_portal.dat B-tree parse that validated itself by byte // accounting); the affected counts were measured on 2026-08-06 by a // separate DatReaderWriter sweep that reproduced FromSetup's steps rather // than calling it. // // They are deliberately NOT derived from the predicates below. A broken // enumeration, a wrong dat path, or a silently-empty decode all satisfy // the affected-count claim vacuously and are caught only by the controls. private const int ExpectedSetups = 5935; private const int ExpectedWithCylinder = 678; private const int ExpectedSphereOnlyNoCylinder = 3605; private const int ExpectedWithoutAnyPrimitive = 1652; private const int ExpectedAffected = 172; private const int ExpectedAffectedCylinderBearing = 73; private const int ExpectedAffectedSphereBearing = 99; private const int ExpectedWithPhysicsBspPart = 530; [Fact] public void InstalledSetups_WithBothAPrimitiveAndAPhysicsBspPart_EmitOnlyBspShapes() { string? datDir = ContentConformanceDats.ResolveDatDir(); if (datDir is null) return; using var dats = new DatCollection(datDir, DatAccessType.Read); // Production physics-BSP predicate, FlatCollisionAssetBuilder.cs:377-380. var physicsBspCache = new Dictionary(); bool HasPhysicsBsp(uint gfxObjId) { if (physicsBspCache.TryGetValue(gfxObjId, out bool cached)) return cached; bool result = dats.Portal.TryGet(gfxObjId, out GfxObj? gfx) && gfx is not null && gfx.Flags.HasFlag(GfxObjFlags.HasPhysics) && gfx.PhysicsBSP?.Root is not null && gfx.VertexArray is not null; physicsBspCache[gfxObjId] = result; return result; } int total = 0; int withCylinder = 0; int sphereOnly = 0; int withoutPrimitive = 0; int withPhysicsBspPart = 0; int affected = 0; int affectedCylinderBearing = 0; int affectedSphereBearing = 0; var affectedThatStillEmitAPrimitive = new List(); foreach (uint id in dats.GetAllIdsOfType()) { if (!dats.Portal.TryGet(id, out Setup? setup) || setup is null) continue; total++; bool hasCylinder = false; foreach (var cyl in setup.CylSpheres) { if (cyl.Radius > 0f) { hasCylinder = true; break; } } bool hasSphere = false; foreach (var sph in setup.Spheres) { if (sph.Radius > 0f) { hasSphere = true; break; } } // FromSetup step 2 is gated on CylSpheres.Count == 0, so a Setup // with both only ever emitted Cylinders. bool emitsSphere = setup.CylSpheres.Count == 0 && hasSphere; if (hasCylinder) withCylinder++; else if (emitsSphere) sphereOnly++; else withoutPrimitive++; bool hasBspPart = false; foreach (uint partId in setup.Parts) { if (HasPhysicsBsp(partId)) { hasBspPart = true; break; } } if (hasBspPart) withPhysicsBspPart++; if (!hasBspPart || !(hasCylinder || emitsSphere)) continue; affected++; if (hasCylinder) affectedCylinderBearing++; else affectedSphereBearing++; // The behaviour: for every affected Setup the production builder // must emit BSP shapes only. IReadOnlyList shapes = ShadowShapeBuilder.FromSetup(setup, 1f, HasPhysicsBsp); bool clean = shapes.Count > 0; foreach (ShadowShape shape in shapes) { if (shape.CollisionType != ShadowCollisionType.BSP) { clean = false; break; } } if (!clean) affectedThatStillEmitAPrimitive.Add(id); } // Positive controls first — without these the claim below is // satisfiable by an empty enumeration. Assert.Equal(ExpectedSetups, total); Assert.Equal(ExpectedWithCylinder, withCylinder); Assert.Equal(ExpectedSphereOnlyNoCylinder, sphereOnly); Assert.Equal(ExpectedWithoutAnyPrimitive, withoutPrimitive); Assert.Equal(ExpectedWithPhysicsBspPart, withPhysicsBspPart); Assert.Equal(ExpectedAffected, affected); Assert.Equal(ExpectedAffectedCylinderBearing, affectedCylinderBearing); Assert.Equal(ExpectedAffectedSphereBearing, affectedSphereBearing); Assert.Empty(affectedThatStillEmitAPrimitive); } }