using AcDream.Core.Physics; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Unit tests for — Commit C of the /// 2026-04-29 live-entity collision port. Covers retail's /// CPhysicsObj::FindObjCollisions exemption block, ported /// line-for-line from /// docs/research/named-retail/acclient_2013_pseudo_c.txt:276782-276839,276971. /// /// /// Behaviour matrix (target / mover columns): /// /// /// MoverTargetSkip? /// anyETHEREAL+IGNORE_COLLISIONSYES (early-out) /// IsViewerIsCreatureYES (camera ray-test passes through) /// IGNORE_CREATURESIsCreatureYES (mover walks through creatures) /// IsPlayerIsPlayer (no PK)YES (non-PK pair walks through) /// IsPlayer + IsPKIsPlayer + IsPKNO (PK pair collides) /// IsPlayer + IsPKLiteIsPlayer + IsPKLiteNO (PKLite pair collides) /// IsPlayerIsPlayer + IsImpenetrableNO (Impenetrable target always collides) /// IsPlayer + IsPKIsPlayer (no PK)YES (mismatched PK skip) /// IsPlayerIsCreature (NPC)NO (player vs NPC always collides) /// /// public class CollisionExemptionTests { private const uint ETHEREAL_PS = 0x4u; private const uint IGNORE_COLLISIONS_PS = 0x10u; [Fact] public void EtherealAndIgnoreCollisions_AlwaysSkipped() { // Target with both bits set is exempted from any mover. Assert.True(CollisionExemption.ShouldSkip( targetState: ETHEREAL_PS | IGNORE_COLLISIONS_PS, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void EtherealOnly_NotSkipped() { // Target with ETHEREAL but NOT IGNORE_COLLISIONS does not bail // out at the first gate — collision proceeds. (Step-down marks // obstruction_ethereal, but does not exempt.) Assert.False(CollisionExemption.ShouldSkip( targetState: ETHEREAL_PS, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void Viewer_VsCreature_Skipped() { // Camera-ray viewer transitions through creatures. Assert.True(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsCreature, moverState: ObjectInfoState.IsViewer)); } [Fact] public void Viewer_VsNonCreature_NotSkipped() { Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.None, moverState: ObjectInfoState.IsViewer)); } [Fact] public void IgnoreCreatures_VsCreature_Skipped() { // Per acclient_2013_pseudo_c.txt:276971 — an arrow with // IGNORE_CREATURES doesn't get blocked by the very monster it's // tracking towards (until missile_ignore filters its target). Assert.True(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsCreature, moverState: ObjectInfoState.IgnoreCreatures)); } [Fact] public void NonPkPlayer_VsNonPkPlayer_Skipped() { // The user-visible payoff: two ordinary players walk through each // other instead of blocking. Assert.True(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void Pk_VsPk_NotSkipped() { // Two PK players collide. Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPK, moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK)); } [Fact] public void PkLite_VsPkLite_NotSkipped() { Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPKLite, moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPKLite)); } [Fact] public void Pk_VsNonPk_Skipped() { // Mismatched PK status: still exempt — only matching pair collides. Assert.True(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer, moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK)); } [Fact] public void Pk_VsPkLite_Skipped() { // PK and PKLite are different pools — pair doesn't match. Assert.True(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsPKLite, moverState: ObjectInfoState.IsPlayer | ObjectInfoState.IsPK)); } [Fact] public void ImpenetrableTarget_VsAnyPlayer_NotSkipped() { // Impenetrable target ("Free" PK status) always collides with // any player mover — regardless of mover's PK state. Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsImpenetrable, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void Player_VsCreature_NotSkipped() { // PvP exemption only applies player-on-player. Player vs creature // (NPC, monster) is the normal blocking case. Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsCreature, moverState: ObjectInfoState.IsPlayer)); } [Fact] public void NonPlayerMover_VsPlayer_NotSkipped() { // PvP rule requires BOTH to be players. Mover is not a player // (e.g., dead-reckoned remote NPC) → no exemption applies. Assert.False(CollisionExemption.ShouldSkip( targetState: 0u, targetFlags: EntityCollisionFlags.IsPlayer, moverState: ObjectInfoState.None)); } }