pathing: 0-replan on long Descend->Traverse + cold-start 5 c2c

Two complementary fixes for live-server "stuck on a step then replan"
on the 252.5,138,220.5 -> 244.5,122,188.5 route.

Search layer (ParkourFeasibility.HasRunUp): a long flat sprint parkour
(5 c2c, horiz~5) cannot launch from a cold start. Vanilla physics show
that gap=4 dy=0 reaches 5.1075m only with 12 momentum ticks of straight
sprint windup; a 0t standing jump tops out at gap=3 (=4 c2c). When the
previous move type is not Parkour/Descend (i.e. no carried airborne
momentum) we now require two aligned back-runway blocks instead of one
so the executor actually has room to spin sprint up.

Execution layer (GroundedSegmentController.ShouldComplete): the
LandingRecovery early-out used to live below the MinExitSpeed gate. A
Descend that landed inside the destination block but naturally settled
to zero speed (e.g. when the next segment is a fresh Traverse rather
than a chained Parkour) would fail the 0.03 MinExitSpeed check and idle
inside the target block until the per-segment timeout fired, triggering
an unnecessary replan. Move the LandingRecovery footprint check above
the speed gate so a fully-decelerated handoff is accepted.

Verified live on 1.21.11-Vanilla:
- 252.5,138,220.5 -> 244.5,122,188.5: 24 segments, 0 replans (was: 1)
- 244.5,122,188.5 -> 252.5,138,220.5: 48 segments, 0 replans
- 251.5,141,210.5 -> 252.5,138,220.5: 34 segments, 0 replans
- 252.5,138,220.5 -> 251.5,141,210.5: 24 segments, 0 replans

Test suite: 297 passed / 22 known pre-existing failures, no new
regressions vs 5de169db.

Made-with: Cursor
This commit is contained in:
BruceChen 2026-04-25 18:08:08 +00:00
parent 5de169db64
commit 8ec1ccd45d
2 changed files with 39 additions and 11 deletions

View file

@ -123,12 +123,14 @@ namespace MinecraftClient.Pathing.Execution.Templates
return TemplateHelper.RemainingDistanceAlongSegment(pos, segment) <= handoffDistance;
}
if (exitSpeed < segment.ExitHints.MinExitSpeed)
return false;
if (exitSpeed > segment.ExitHints.MaxExitSpeed)
return false;
// LandingRecovery accepts a fully-decelerated handoff: once the bot
// has reached the target block on the ground, the segment has done
// its job. Apply this before the MinExitSpeed gate so a Descend
// that lands and naturally settles to zero speed (e.g. when the
// following segment is a fresh Traverse rather than a chained
// Parkour) can hand off cleanly. Without this early-out the bot
// would idle inside the destination block until the segment timed
// out, triggering an unnecessary replan.
if (segment.ExitTransition == PathTransitionType.LandingRecovery
&& physics.OnGround
&& !segment.ExitHints.RequireStableFooting
@ -137,6 +139,12 @@ namespace MinecraftClient.Pathing.Execution.Templates
return true;
}
if (exitSpeed < segment.ExitHints.MinExitSpeed)
return false;
if (exitSpeed > segment.ExitHints.MaxExitSpeed)
return false;
if (segment.ExitHints.RequireStableFooting)
{
return physics.OnGround

View file

@ -62,11 +62,31 @@ internal static class ParkourFeasibility
if (carriedEntry && yDelta < 0)
return true;
int backX = x - Math.Sign(xOffset);
int backZ = z - Math.Sign(zOffset);
if (!ctx.CanWalkOn(backX, y - 1, backZ))
return false;
return IsColumnPassable(ctx, backX, y, backZ);
int xSign = Math.Sign(xOffset);
int zSign = Math.Sign(zOffset);
// Long flat sprint parkour (5 c2c, horiz~5) requires the player to be
// launched at full vanilla sprint velocity (~12 momentum ticks). A
// standing-jump cold start only reaches gap=3 (=4 c2c). When the
// previous move is not a momentum-carrying Parkour/Descend, one back
// block of runway is not enough to spin sprint up; demand at least
// two aligned back blocks so the executor has a real run-up window.
// tools/sim_jump_reach.py "Standing sprint jump (0t momentum)" matrix
// shows gap=4 dy=0 is unreachable, while 12t-momentum gap=4 reaches
// 5.1075 m.
int requiredBackBlocks = (yDelta == 0 && !carriedEntry && horiz >= 4.5) ? 2 : 1;
for (int i = 1; i <= requiredBackBlocks; i++)
{
int backX = x - xSign * i;
int backZ = z - zSign * i;
if (!ctx.CanWalkOn(backX, y - 1, backZ))
return false;
if (!IsColumnPassable(ctx, backX, y, backZ))
return false;
}
return true;
}
public static bool TryGetRequiredStaticEntryRunupSteps(