using AcDream.Core.Physics; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Unit tests for the obstruction_ethereal mechanism ported verbatim /// from retail in Task 3 of the collision-inclusion phase. /// /// /// Retail oracle: CPhysicsObj::FindObjCollisions @ 0x0050f050 /// (acclient_2013_pseudo_c.txt:276782): /// /// pc:276782 — Gate 1: instant-skip requires BOTH ETHEREAL_PS (0x4) AND /// IGNORE_COLLISIONS_PS (0x10). ETHEREAL alone does NOT instant-skip. /// pc:276806 — ETHEREAL-alone sets sphere_path.obstruction_ethereal = 1 /// and continues to the shape dispatch. /// pc:276989 — After the per-object shape test, retail clears /// obstruction_ethereal = 0. /// /// /// /// /// Consume site: BSPTREE::find_collisions @ 0x0053a496 /// (acclient_2013_pseudo_c.txt:323742): Path 1 fires when /// insert_type == PLACEMENT_INSERT || obstruction_ethereal != 0, /// routing to sphere_intersects_solid (passable-ethereal) instead /// of the blocking paths. /// /// /// /// Retail divergence register: row AD-7 retired in this commit — the /// ETHEREAL-alone shim is replaced by the faithful port. /// /// public class ObstructionEtherealTests { private const uint ETHEREAL_PS = 0x4u; private const uint IGNORE_COLLISIONS_PS = 0x10u; // ── Gate-1 tests: ShouldSkip dual-bit contract ───────────────────────── [Fact] public void ShouldSkip_BothBits_InstantSkip() { // ETHEREAL | IGNORE_COLLISIONS together → instant-skip (Gate 1 fires). // Retail pc:276782: `if ((state & 4) AND (state & 0x10)) return 1`. Assert.True(CollisionExemption.ShouldSkip( targetState: ETHEREAL_PS | IGNORE_COLLISIONS_PS, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void ShouldSkip_EtherealAlone_NotInstantSkip() { // ETHEREAL alone (0x4) must NOT instant-skip — retail takes the // obstruction_ethereal branch instead. The old AD-7 shim is retired. // Retail pc:276782: only the COMBINED gate returns early; ETHEREAL-alone // falls through to set obstruction_ethereal and run the shape test. Assert.False(CollisionExemption.ShouldSkip( targetState: ETHEREAL_PS, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void ShouldSkip_IgnoreCollisionsAlone_NotSkipped() { // IGNORE_COLLISIONS alone (0x10) without ETHEREAL → not instant-skipped; // falls through to shape dispatch (no obstruction_ethereal set either). Assert.False(CollisionExemption.ShouldSkip( targetState: IGNORE_COLLISIONS_PS, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsPlayer)); } // ── SpherePath.ObstructionEthereal field ─────────────────────────────── [Fact] public void SpherePath_HasObstructionEtherealField() { // Compile-time check that the field was added to SpherePath. var sp = new SpherePath(); Assert.False(sp.ObstructionEthereal); // default is false sp.ObstructionEthereal = true; Assert.True(sp.ObstructionEthereal); } // ── BSPQuery Path-1 gate: ObstructionEthereal routes through // sphere_intersects_solid (not the blocking slide path) ────────────── // // A full transition-level harness for the BSP path requires a real // BSP tree fixture and is deferred to the DoorCollisionApparatusTests // integration tests. Here we verify the GATE condition in isolation // via the FindEnvCollisions D5 clear and the SpherePath property // round-trip. The DoorCollisionApparatusTests and CellarUpTrajectory- // ReplayTests are the regression guards for the consume-site change. }