2026-04-12 18:46:42 +08:00
|
|
|
using MinecraftClient.Mapping;
|
|
|
|
|
using MinecraftClient.Pathing.Core;
|
|
|
|
|
using MinecraftClient.Pathing.Execution;
|
|
|
|
|
using MinecraftClient.Physics;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace MinecraftClient.Tests.Pathing.Execution;
|
|
|
|
|
|
|
|
|
|
public sealed class PathExecutorCompletionTests
|
|
|
|
|
{
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Tick_ClearsMovementInput_WhenSegmentCompletes()
|
|
|
|
|
{
|
|
|
|
|
var executor = new PathExecutor(new List<PathSegment>
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Start = new Location(0.5, 80, 0.5),
|
|
|
|
|
End = new Location(1.5, 80, 0.5),
|
|
|
|
|
MoveType = MoveType.Traverse
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var physics = new PlayerPhysics
|
|
|
|
|
{
|
|
|
|
|
Yaw = 270f,
|
2026-04-12 21:33:24 +08:00
|
|
|
Pitch = 0f,
|
|
|
|
|
OnGround = true
|
2026-04-12 18:46:42 +08:00
|
|
|
};
|
2026-04-13 15:36:09 +00:00
|
|
|
var input = new MovementInput
|
|
|
|
|
{
|
|
|
|
|
Forward = true,
|
|
|
|
|
Sprint = true,
|
|
|
|
|
Jump = true,
|
|
|
|
|
Back = true
|
|
|
|
|
};
|
2026-04-12 18:46:42 +08:00
|
|
|
var pos = new Location(1.48, 80, 0.5);
|
|
|
|
|
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
|
|
|
|
|
|
|
|
|
var state = executor.Tick(pos, physics, input, world);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(PathExecutorState.Complete, state);
|
|
|
|
|
Assert.False(input.Forward);
|
|
|
|
|
Assert.False(input.Sprint);
|
|
|
|
|
Assert.False(input.Jump);
|
|
|
|
|
Assert.False(input.Back);
|
|
|
|
|
}
|
2026-04-13 15:36:09 +00:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Tick_CompletesStraightThreeSegmentFlatPath()
|
|
|
|
|
{
|
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(BuildNodes(
|
|
|
|
|
(100, 80, 100, MoveType.Traverse),
|
|
|
|
|
(101, 80, 100, MoveType.Traverse),
|
|
|
|
|
(102, 80, 100, MoveType.Traverse),
|
|
|
|
|
(103, 80, 100, MoveType.Traverse)));
|
|
|
|
|
|
|
|
|
|
var executor = new PathExecutor(segments);
|
|
|
|
|
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segments[0].Start, yaw: 270f);
|
|
|
|
|
var input = new MovementInput();
|
|
|
|
|
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115);
|
|
|
|
|
|
|
|
|
|
PathExecutorState state = PathExecutorState.InProgress;
|
|
|
|
|
for (int tick = 0; tick < 260; tick++)
|
|
|
|
|
{
|
|
|
|
|
input.Reset();
|
|
|
|
|
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
|
|
|
|
state = executor.Tick(pos, physics, input, world);
|
|
|
|
|
if (state != PathExecutorState.InProgress)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
physics.ApplyInput(input);
|
|
|
|
|
physics.Tick(world);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.Equal(PathExecutorState.Complete, state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Tick_ShortAcceptedPath_FromLiveSegmentZeroDriftState_CompletesWithoutFailure()
|
|
|
|
|
{
|
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(BuildNodes(
|
|
|
|
|
(100, 80, 100, MoveType.Traverse),
|
|
|
|
|
(101, 80, 100, MoveType.Traverse),
|
|
|
|
|
(102, 80, 100, MoveType.Traverse),
|
|
|
|
|
(103, 80, 100, MoveType.Traverse)));
|
|
|
|
|
|
|
|
|
|
var debugLogs = new List<string>();
|
|
|
|
|
var executor = new PathExecutor(segments, debugLogs.Add);
|
|
|
|
|
var physics = new PlayerPhysics
|
|
|
|
|
{
|
|
|
|
|
Position = new Vec3d(101.56, 80.00, 100.74),
|
|
|
|
|
DeltaMovement = Vec3d.Zero,
|
|
|
|
|
OnGround = true,
|
|
|
|
|
MovementSpeed = 0.1f,
|
|
|
|
|
Yaw = 270f,
|
|
|
|
|
Pitch = 0f
|
|
|
|
|
};
|
|
|
|
|
var input = new MovementInput();
|
|
|
|
|
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115);
|
|
|
|
|
|
|
|
|
|
PathExecutorState state = PathExecutorState.InProgress;
|
|
|
|
|
for (int tick = 0; tick < 220; tick++)
|
|
|
|
|
{
|
|
|
|
|
input.Reset();
|
|
|
|
|
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
|
|
|
|
state = executor.Tick(pos, physics, input, world);
|
|
|
|
|
if (state != PathExecutorState.InProgress)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
physics.ApplyInput(input);
|
|
|
|
|
physics.Tick(world);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(state == PathExecutorState.Complete, $"state={state}\n{string.Join('\n', debugLogs)}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<PathNode> BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<PathNode>(raw.Length);
|
|
|
|
|
for (int i = 0; i < raw.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var node = new PathNode(raw[i].x, raw[i].y, raw[i].z);
|
|
|
|
|
if (i > 0)
|
|
|
|
|
node.MoveUsed = raw[i].moveUsed;
|
|
|
|
|
result.Add(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2026-04-12 18:46:42 +08:00
|
|
|
}
|