mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-29 13:04:59 +00:00
pathing: route Descend->turn handoff through LandingRecovery hints
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
This commit is contained in:
parent
1c2e6fba2b
commit
b35cdfc40f
3 changed files with 94 additions and 15 deletions
|
|
@ -94,20 +94,21 @@ namespace MinecraftClient.Pathing.Execution
|
|||
bool nextImmediatelyJumps = nextNext is not null
|
||||
&& nextNext.MoveType is (MoveType.Parkour or MoveType.Ascend);
|
||||
|
||||
if (turning)
|
||||
{
|
||||
return new PathTransitionHints(
|
||||
DesiredHeadingX: next.HeadingX,
|
||||
DesiredHeadingZ: next.HeadingZ,
|
||||
MinExitSpeed: nextImmediatelyJumps ? 0.05 : 0.0,
|
||||
MaxExitSpeed: nextImmediatelyJumps ? 0.16 : 0.05,
|
||||
RequireStableFooting: !nextImmediatelyJumps,
|
||||
RequireGrounded: true,
|
||||
RequireJumpReady: nextImmediatelyJumps,
|
||||
AllowAirBrake: true,
|
||||
HorizonTicks: 12);
|
||||
}
|
||||
|
||||
// LandingRecovery (current is Descend/Parkour/Fall) takes precedence
|
||||
// over the turning branch even when heading changes between current
|
||||
// and next. The turning branch demands RequireStableFooting=true,
|
||||
// which forces the GroundedSegmentController completion gate to wait
|
||||
// for IsSettledOnTargetBlock (footprint inside, won't leave next
|
||||
// tick, horizontal speed^2 <= 0.0016). After a multi-block diagonal
|
||||
// Descend the bot lands inside the target block already carrying
|
||||
// ~0.02 m/tick of residual momentum that the planner can't shed
|
||||
// cleanly: the per-tick yaw bias toward the next segment's heading
|
||||
// pulls the bot off-axis, the bot slides off the target block, the
|
||||
// template re-targets the centre, and so on for ~60 ticks until
|
||||
// momentum decays. The LandingRecovery shortcut in
|
||||
// GroundedSegmentController.ShouldComplete (!RequireStableFooting +
|
||||
// footprint inside target) bypasses the speed gate cleanly the
|
||||
// moment the bot reaches the landing column.
|
||||
if (exitTransition == PathTransitionType.LandingRecovery)
|
||||
{
|
||||
return new PathTransitionHints(
|
||||
|
|
@ -122,6 +123,20 @@ namespace MinecraftClient.Pathing.Execution
|
|||
HorizonTicks: 12);
|
||||
}
|
||||
|
||||
if (turning)
|
||||
{
|
||||
return new PathTransitionHints(
|
||||
DesiredHeadingX: next.HeadingX,
|
||||
DesiredHeadingZ: next.HeadingZ,
|
||||
MinExitSpeed: nextImmediatelyJumps ? 0.05 : 0.0,
|
||||
MaxExitSpeed: nextImmediatelyJumps ? 0.16 : 0.05,
|
||||
RequireStableFooting: !nextImmediatelyJumps,
|
||||
RequireGrounded: true,
|
||||
RequireJumpReady: nextImmediatelyJumps,
|
||||
AllowAirBrake: true,
|
||||
HorizonTicks: 12);
|
||||
}
|
||||
|
||||
return new PathTransitionHints(
|
||||
DesiredHeadingX: next.HeadingX,
|
||||
DesiredHeadingZ: next.HeadingZ,
|
||||
|
|
|
|||
|
|
@ -51,10 +51,12 @@ namespace MinecraftClient.Pathing.Execution
|
|||
/// </summary>
|
||||
public static bool DiagnosticsEnabled { get; set; }
|
||||
|
||||
private const int DiagnosticsTailSize = 64;
|
||||
private const int DiagnosticsTailSize = 200;
|
||||
private const int SlowSegmentDumpTickThreshold = 25;
|
||||
private readonly Queue<string> _diagnosticsTail = new(DiagnosticsTailSize + 1);
|
||||
private PathResult? _lastPlan;
|
||||
private int _lastObservedSegmentIndex = -1;
|
||||
private int _ticksSinceSegmentStart;
|
||||
|
||||
public bool IsNavigating =>
|
||||
(_executor is not null && !_executor.IsComplete)
|
||||
|
|
@ -89,6 +91,7 @@ namespace MinecraftClient.Pathing.Execution
|
|||
var segments = PathSegmentBuilder.FromPath(result.Path);
|
||||
_executor = new PathExecutor(segments, _debugLog, _observer);
|
||||
_lastObservedSegmentIndex = -1;
|
||||
_ticksSinceSegmentStart = 0;
|
||||
_infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments");
|
||||
}
|
||||
|
||||
|
|
@ -197,11 +200,32 @@ namespace MinecraftClient.Pathing.Execution
|
|||
// without relying on the bounded tail buffer. Resets on plan install.
|
||||
if (segIdx != _lastObservedSegmentIndex)
|
||||
{
|
||||
if (_lastObservedSegmentIndex >= 0
|
||||
&& _ticksSinceSegmentStart >= SlowSegmentDumpTickThreshold)
|
||||
{
|
||||
_infoLog?.Invoke(
|
||||
$"[PathDiag] Slow segment {_lastObservedSegmentIndex}/{_executor.TotalSegments} took {_ticksSinceSegmentStart} ticks, dumping last {Math.Min(_diagnosticsTail.Count, _ticksSinceSegmentStart)} ticks:");
|
||||
int toDump = Math.Min(_diagnosticsTail.Count, _ticksSinceSegmentStart);
|
||||
int skipCount = _diagnosticsTail.Count - toDump;
|
||||
int i = 0;
|
||||
foreach (string line in _diagnosticsTail)
|
||||
{
|
||||
if (i++ < skipCount)
|
||||
continue;
|
||||
_infoLog?.Invoke($"[PathDiag] t-{toDump - (i - skipCount)}: {line}");
|
||||
}
|
||||
}
|
||||
|
||||
_lastObservedSegmentIndex = segIdx;
|
||||
_ticksSinceSegmentStart = 0;
|
||||
_infoLog?.Invoke(
|
||||
$"[PathDiag] seg->{segIdx}/{_executor.TotalSegments} pos=({pos.X:F2},{pos.Y:F2},{pos.Z:F2}) yaw={physics.Yaw:F1} vy={physics.DeltaMovement.Y:F3} og={physics.OnGround} " +
|
||||
(seg is null ? "none" : $"{seg.MoveType} ({seg.Start.X:F1},{seg.Start.Y:F1},{seg.Start.Z:F1})->({seg.End.X:F1},{seg.End.Y:F1},{seg.End.Z:F1}) exit={seg.ExitTransition}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_ticksSinceSegmentStart++;
|
||||
}
|
||||
|
||||
_diagnosticsTail.Enqueue(
|
||||
$"pos=({pos.X:F2},{pos.Y:F2},{pos.Z:F2}) yaw={physics.Yaw:F1} vy={physics.DeltaMovement.Y:F3} vx={physics.DeltaMovement.X:F3} vz={physics.DeltaMovement.Z:F3} og={physics.OnGround} {segStr}");
|
||||
|
|
@ -428,6 +452,7 @@ namespace MinecraftClient.Pathing.Execution
|
|||
_lastPlan = result;
|
||||
_diagnosticsTail.Clear();
|
||||
_lastObservedSegmentIndex = -1;
|
||||
_ticksSinceSegmentStart = 0;
|
||||
if (isInitial)
|
||||
{
|
||||
_executor = new PathExecutor(segments, _debugLog, _observer);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue