Files
kronic d77649fe16 Tests: MC-4 combo + cone-math EditMode coverage
23 plain-Entities tests: cone predicate (range/bearing/coincident/planar), combo advance/chain-window/lockout/reset/idempotency, movement-commit + SwingStartTick rollback lower-bound, dash precedence (active window + same-tick tie + recovery tail), server cleave (cone select, finisher scaling, knockback, dead-exclusion, client-no-damage), comboLen=2 finisher, two-player co-op cleave, death-clears. 294/294 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:23:10 -07:00

69 lines
2.5 KiB
C#

using NUnit.Framework;
using ProjectM.Simulation;
using Unity.Mathematics;
namespace ProjectM.Tests
{
/// <summary>
/// MC-4 — pure cone hit-test (<see cref="MeleeConeMath.InCone"/>): the planar XZ range gate + the
/// dot(bearing, facing) &gt;= cos(halfAngle) cone gate, plus the coincident / zero-facing / non-positive-range
/// guards. Shares the exact predicate AutoTarget uses, but as a per-candidate boolean for the collect-all cleave.
/// </summary>
public class MeleeConeMathTests
{
const float Cos60 = 0.5f; // cos(60deg): half-angle 60 -> 120deg full cone
[Test]
public void In_Range_And_Straight_Ahead_Hits()
{
Assert.IsTrue(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 3f, Cos60, new float3(0, 0, 2)));
}
[Test]
public void Out_Of_Range_Misses()
{
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 3f, Cos60, new float3(0, 0, 4)));
}
[Test]
public void At_90_Degrees_Outside_Cone_Misses()
{
// bearing +X vs facing +Z: dot = 0 < cos60 (0.5) -> outside the cone.
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 3f, Cos60, new float3(2, 0, 0)));
}
[Test]
public void At_45_Degrees_Inside_Cone_Hits()
{
// bearing ~45deg: dot ~0.707 > 0.5 -> inside.
Assert.IsTrue(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 5f, Cos60, new float3(2, 0, 2)));
}
[Test]
public void Behind_The_Player_Misses()
{
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 5f, Cos60, new float3(0, 0, -2)));
}
[Test]
public void Coincident_Target_Excluded()
{
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 3f, Cos60, float3.zero));
}
[Test]
public void Zero_Facing_Or_NonPositive_Range_Misses()
{
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, float2.zero, 3f, Cos60, new float3(0, 0, 2)), "zero facing -> miss");
Assert.IsFalse(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 0f, Cos60, new float3(0, 0, 1)), "range 0 -> miss");
}
[Test]
public void Uses_Planar_XZ_And_Ignores_Y()
{
// straight ahead in XZ but far in Y: still a hit (Y ignored, top-down).
Assert.IsTrue(MeleeConeMath.InCone(float3.zero, new float2(0, 1), 3f, Cos60, new float3(0, 5, 2)));
}
}
}