tests/Runtime/HexMovementValidatorTests.cs

Unit tests for HexMovementValidator in the runtime. Exercises various movement validation cases (horizontal speed, large teleports, frozen state tolerance, non-finite positions, vertical rise/fall, step allowances and jump limits) by calling Evaluate and asserting whether corrections occur.

NetworkingFile Access
#nullable enable

using Hexagon.V2.Runtime;

namespace Hexagon.V2.Tests.Runtime;

[TestClass]
public sealed class HexMovementValidatorTests
{
	private const float RunSpeed = 320f;
	private const float JumpSpeed = 300f;
	private const float Dt = 1f / 30f;

	private static MovementSample At( float x, float y, float z ) => new( x, y, z );

	[TestMethod]
	public void AcceptsMovementWithinTheRunSpeedEnvelope()
	{
		// Horizontal envelope ≈ 320 * 1.25 * (1/30) + 4 ≈ 17 units per tick.
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 9, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsFalse( decision.Corrected );
	}

	[TestMethod]
	public void CorrectsHorizontalMovementBeyondTheEnvelope()
	{
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 200, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void CorrectsHardTeleportEvenAcrossALargeStep()
	{
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 1000, 0, 0 ), 1f, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void FreezeCorrectsMovementButToleratesSkinJitter()
	{
		var moved = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 40, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: true );
		Assert.IsTrue( moved.Corrected );

		var jitter = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 2, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: true );
		Assert.IsFalse( jitter.Corrected );
	}

	[TestMethod]
	public void CorrectsNonFinitePosition()
	{
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( float.NaN, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void AcceptsAFallWithinTheTerminalEnvelope()
	{
		// Falling envelope ≈ 1800 * (1/30) + 4 ≈ 64 units per tick.
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 0, 0, -40 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsFalse( decision.Corrected );
	}

	[TestMethod]
	public void CorrectsImpossibleVerticalRise()
	{
		// Rise envelope ≈ 300 * 1.5 * (1/30) + 4 ≈ 19 units per tick (no step-rise without motion).
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 0, 0, 200 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void RejectsHorizontalSpeedTheLooseSkinWouldHaveAllowed()
	{
		// 24 units/tick: within the old +16 skin (≈29), beyond the tightened +4 skin (≈17).
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 24, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void FrozenPlayerIsCorrectedBeyondTheTightFrozenSkin()
	{
		// 10 units/tick while frozen: the loose skin tolerated it; the frozen skin (2) does not.
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 10, 0, 0 ), Dt, RunSpeed, JumpSpeed, frozen: true );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void RejectsStraightUpFlightWithoutHorizontalMotion()
	{
		// 30 units of pure vertical rise: with no horizontal motion there is no step allowance, so
		// only jump physics apply (≈19/tick) and this is corrected — a client cannot fly straight up.
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 0, 0, 30 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsTrue( decision.Corrected );
	}

	[TestMethod]
	public void AllowsAStepUpWhileMovingHorizontally()
	{
		// An 18-unit rise is legitimate when paired with horizontal movement (a stair/slope), so the
		// discrete step allowance keeps it smooth.
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 6, 0, 18 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsFalse( decision.Corrected );
	}

	[TestMethod]
	public void AcceptsANormalJumpRise()
	{
		var decision = HexMovementValidator.Evaluate(
			At( 0, 0, 0 ), At( 0, 0, 10 ), Dt, RunSpeed, JumpSpeed, frozen: false );
		Assert.IsFalse( decision.Corrected );
	}
}