mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
When a Descend segment is followed by another Descend (or Traverse/ Diagonal) with a different heading, PathSegmentBuilder.Classify correctly assigned ExitTransition=LandingRecovery, but BuildHints fell into the `if (turning)` branch first and returned hints with RequireStableFooting=true. That gate forces GroundedSegmentController to wait for IsSettledOnTargetBlock (footprint inside, won't leave next tick, horizontal speed^2 <= 0.0016), so a multi-block diagonal Descend that landed inside the target block while still carrying ~0.02 m/tick of residual jump momentum oscillated in place for ~60 ticks (3 seconds) until the speed decayed. Move the LandingRecovery branch ahead of the turning branch so the Descend-carry handoff uses RequireStableFooting=false and the ShouldComplete shortcut (LandingRecovery + footprint inside target on the ground) fires the moment the bot reaches the landing column. Adds two regression tests covering the Descend->turning-Descend handoff and a sanity guard that ordinary Traverse->turning-Traverse still uses the turning branch. Also lifts DiagnosticsTailSize from 64 to 200 and emits an automatic "slow segment" tick dump from PathSegmentManager whenever a segment takes >=25 ticks, which is what surfaced this stall. Made-with: Cursor
119 lines
4.2 KiB
C#
119 lines
4.2 KiB
C#
using System.Collections.Generic;
|
|
using MinecraftClient.Pathing.Core;
|
|
using MinecraftClient.Pathing.Execution;
|
|
using Xunit;
|
|
|
|
namespace MinecraftClient.Tests.Pathing.Execution;
|
|
|
|
public sealed class PathSegmentBuilderTests
|
|
{
|
|
[Fact]
|
|
public void FromPath_AnnotatesStraightTraverse_AsContinueStraight()
|
|
{
|
|
var nodes = BuildNodes(
|
|
(0, 80, 0, MoveType.Traverse),
|
|
(1, 80, 0, MoveType.Traverse),
|
|
(2, 80, 0, MoveType.Traverse));
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(nodes);
|
|
|
|
Assert.Equal(PathTransitionType.ContinueStraight, segments[0].ExitTransition);
|
|
Assert.True(segments[0].PreserveSprint);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromPath_AnnotatesOrthogonalTraverse_AsTurn()
|
|
{
|
|
var nodes = BuildNodes(
|
|
(0, 80, 0, MoveType.Traverse),
|
|
(1, 80, 0, MoveType.Traverse),
|
|
(1, 80, 1, MoveType.Traverse));
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(nodes);
|
|
|
|
Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition);
|
|
Assert.False(segments[0].PreserveSprint);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromPath_AnnotatesTraverseIntoParkour_AsPrepareJump()
|
|
{
|
|
var nodes = BuildNodes(
|
|
(120, 80, 110, MoveType.Traverse),
|
|
(121, 80, 110, MoveType.Traverse),
|
|
(123, 80, 110, MoveType.Parkour));
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(nodes);
|
|
|
|
Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition);
|
|
Assert.True(segments[0].PreserveSprint);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromPath_DescendIntoTurningDescend_UsesLandingRecoveryHints()
|
|
{
|
|
// Regression: a Descend that lands and immediately steps into a
|
|
// perpendicular Descend (different heading) used to receive the
|
|
// turning-branch hints with RequireStableFooting=true. That gate forces
|
|
// GroundedSegmentController to wait for IsSettledOnTargetBlock, which
|
|
// takes ~3 seconds while residual jump momentum decays. The
|
|
// LandingRecovery branch (RequireStableFooting=false) lets the
|
|
// ShouldComplete shortcut fire as soon as the bot's footprint is
|
|
// inside the landing block.
|
|
var nodes = BuildNodes(
|
|
(255, 137, 220, MoveType.Traverse),
|
|
(256, 134, 219, MoveType.Descend),
|
|
(256, 132, 217, MoveType.Descend));
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(nodes);
|
|
|
|
Assert.Equal(PathTransitionType.LandingRecovery, segments[0].ExitTransition);
|
|
Assert.False(segments[0].ExitHints.RequireStableFooting);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromPath_TraverseIntoTurningTraverse_StillUsesTurnHints()
|
|
{
|
|
// Sanity guard: ordinary Traverse → turning-Traverse must still use the
|
|
// turning branch (StableFooting=true) — only Descend/Parkour/Fall
|
|
// sources should bypass it.
|
|
var nodes = BuildNodes(
|
|
(0, 80, 0, MoveType.Traverse),
|
|
(1, 80, 0, MoveType.Traverse),
|
|
(1, 80, 1, MoveType.Traverse));
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath(nodes);
|
|
|
|
Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition);
|
|
Assert.True(segments[0].ExitHints.RequireStableFooting);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromPath_CopiesParkourProfile_ToRuntimeSegment()
|
|
{
|
|
var start = new PathNode(100, 80, 100);
|
|
var end = new PathNode(99, 80, 102)
|
|
{
|
|
MoveUsed = MoveType.Parkour,
|
|
ParkourProfile = ParkourProfile.Sidewall
|
|
};
|
|
|
|
List<PathSegment> segments = PathSegmentBuilder.FromPath([start, end]);
|
|
|
|
Assert.Single(segments);
|
|
Assert.Equal(ParkourProfile.Sidewall, segments[0].ParkourProfile);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|