using System;
using System.Collections.Generic;
using AcDream.Core.Physics;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
using DatReaderWriter.Types;
using Xunit;
namespace AcDream.Core.Tests.Physics;
///
/// Guards the DAT-only shape-source rule (Slice 1 of the unified
/// collision-inclusion phase, 2026-06-24).
///
///
/// Retail oracle: CPartArray::InitParts@0x00517F40 (parts come from
/// the Setup list only), CGfxObj::Serialize@0x00534970 (physics_bsp
/// gated on serialized-flags bit-0), CPhysicsPart::find_obj_collisions@0x0050D8D0
/// (returns OK / passable when physics_bsp == null). Render-mesh bounds
/// never enter collision.
///
///
public class ShadowShapeBuilderShapeSourceTests
{
// Retail: an object with no DAT physics shape (no CylSphere, no Sphere,
// no part with a PhysicsBSP) emits NO collision shape and is passable.
// CPhysicsPart::find_obj_collisions@0x0050D8D0 returns OK when physics_bsp==null.
[Fact]
public void Setup_WithNoCylSpheres_NoSpheres_NoPhysicsBspParts_YieldsEmptyShapeList()
{
var setup = new Setup
{
CylSpheres = new List(),
Spheres = new List(),
Parts = { 0x01000ABCu },
PlacementFrames = new Dictionary(),
};
var shapes = ShadowShapeBuilder.FromSetup(setup, entScale: 1f, hasPhysicsBsp: _ => false);
Assert.Empty(shapes);
}
// D4+W1 (2026-06-24) — BSP-only furniture weenie guard.
//
// A Setup with no CylSpheres, no Spheres, no Radius, but whose Part has a
// PhysicsBSP (e.g. a candle holder or floor candelabra) MUST produce a BSP
// ShadowShape. The premature `if (!hasCyl && !hasSphere && !hasRadius) return`
// gate in GameWindow.RegisterLiveEntityCollision was dropped because it fired
// BEFORE ShadowShapeBuilder ran and discarded these entities entirely.
//
// Retail oracle: CPhysicsPart::find_obj_collisions@0x0050D8D0 -- when
// physics_bsp is non-null the part IS tested; the outer loop in
// CPartArray::FindObjCollisions iterates all parts regardless of
// CylSpheres/Spheres. ShadowShapeBuilder.FromSetup mirrors this by emitting
// one BSP shape per part that `hasPhysicsBsp` returns true for.
[Fact]
public void Setup_WithBspPart_NoCylSpheres_EmitsBspShape()
{
// hasPhysicsBsp predicate returns true for 0x0100AAAAu, simulating a
// GfxObj that has a PhysicsBSP but no CylSpheres/Spheres.
const uint BspGfxObjId = 0x0100AAAAu;
var setup = new Setup
{
CylSpheres = new List(),
Spheres = new List(),
Parts = { BspGfxObjId },
PlacementFrames = new Dictionary(),
};
var shapes = ShadowShapeBuilder.FromSetup(setup, entScale: 1f,
hasPhysicsBsp: id => id == BspGfxObjId);
Assert.Contains(shapes, s => s.CollisionType == ShadowCollisionType.BSP);
}
// Complementary regression guard: a Setup with no CylSpheres, no Spheres,
// and a Part that has NO PhysicsBSP must still produce an empty shape list.
// The premature gate removal does not accidentally register shapeless entities.
[Fact]
public void Setup_WithPartButNoBsp_NoCylSpheres_YieldsEmptyShapeList()
{
const uint NoBspGfxObjId = 0x0100BBBBu;
var setup = new Setup
{
CylSpheres = new List(),
Spheres = new List(),
Parts = { NoBspGfxObjId },
PlacementFrames = new Dictionary(),
};
var shapes = ShadowShapeBuilder.FromSetup(setup, entScale: 1f,
hasPhysicsBsp: _ => false);
Assert.Empty(shapes);
}
}