mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
pathing: lock Descend air-yaw and shortcut Ascend->Turn landing
Two coupled fixes for live 258<->237 / 237<->244 routes that previously hit 5 replans and gave up on Ascend exit=Turn segments. DescendTemplate: when biasTowardExitInAir is false on a multi-block diagonal descend, the per-tick targetYaw rotates as the bot drifts past the landing column mid-fall (e.g. dx=-1 dz=-1 drop yaw cycles 135 -> 90 -> 0 -> 315 over six air ticks). With Forward held, that rotating yaw pushes air-control momentum perpendicular to the planned trajectory, sliding the bot ~0.5 m off the landing onto an adjacent block one tier below. Lock airborne yaw to the segment's start->end heading for those descends so air drift stays aligned with the diagonal. AscendTemplate: add a post-landing completion shortcut for non-FinalStop exits. Once the jump arc puts the bot back on ground at the target's elevation with its center inside the target column, hand off to the next template (which snaps yaw on its first tick). Holding the segment runs both AscendTemplate's top-level yaw smoothing toward a moving targetYaw AND GroundedSegmentController's segment/exit-heading rotation each tick. The competing yaw targets oscillate the bot ~80 ticks until it walks off the 1-block landing's edge and the segment fails. Mirrors the existing PrepareJump completion gate. FinalStop is excluded so the last segment still uses IsSettledAtEnd to detect a true stop. Live verification on 1.21.11 (round-trip 244<->237 plus the originally failing 237->244 route): four navigations, 0 replans, all 25/41/24/41 segments completed. Made-with: Cursor
This commit is contained in:
parent
512693dcd0
commit
c1831b2730
3 changed files with 173 additions and 3 deletions
|
|
@ -886,4 +886,121 @@ public sealed class GroundedTemplateConvergenceTests
|
|||
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, ascend.End),
|
||||
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live regression: B->A route segment 9/41 Ascend (243.5,97,181.5)->
|
||||
/// (244.5,98,182.5) exit=Turn, fed by a Descend->PrepareJump handoff.
|
||||
/// The Ascend starts mid-air with cardinal +Z momentum and yaw aimed
|
||||
/// at the diagonal target. Without a post-landing completion shortcut
|
||||
/// the bot lands hot (footprint partially outside target) and the
|
||||
/// AscendTemplate top-level yaw smoothing toward targetYaw fights the
|
||||
/// GroundedSegmentController exit-heading rotation, oscillating yaw
|
||||
/// each tick until the bot drifts off the 1-block landing's edge and
|
||||
/// the segment fails. With the shortcut the segment completes the
|
||||
/// instant the bot lands with center inside the target column, which
|
||||
/// hands off cleanly to the next template (Traverse) that snaps yaw
|
||||
/// on its first tick.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AscendTemplate_TurnExit_FromCarriedAirMomentum_CompletesOnTargetColumnLanding()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: -4, max: 6);
|
||||
FlatWorldTestBuilder.ClearBox(world, -4, 80, -4, 6, 86, 6);
|
||||
// Source block (0,79,0) is the Descend's landing column; the bot
|
||||
// arrives mid-jump above it.
|
||||
FlatWorldTestBuilder.SetSolid(world, 0, 79, 0);
|
||||
// Target block (1,80,1) (NE diagonal +1y) is the Ascend's landing.
|
||||
FlatWorldTestBuilder.SetSolid(world, 1, 80, 1);
|
||||
// Next-segment landing column (Traverse east from target).
|
||||
FlatWorldTestBuilder.SetSolid(world, 2, 80, 1);
|
||||
// Walkable shoulder block beneath the bot's overshooting +Z
|
||||
// footprint so the bot doesn't drop into a 2-block hole on
|
||||
// landing (matches the live world geometry where a wide platform
|
||||
// existed at the target's elevation).
|
||||
FlatWorldTestBuilder.SetSolid(world, 1, 80, 2);
|
||||
|
||||
var ascend = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 81, 1.5),
|
||||
MoveType = MoveType.Ascend,
|
||||
ExitTransition = PathTransitionType.Turn,
|
||||
ExitHints = new PathTransitionHints(
|
||||
DesiredHeadingX: 1,
|
||||
DesiredHeadingZ: 0,
|
||||
MinExitSpeed: 0.0,
|
||||
MaxExitSpeed: 0.05,
|
||||
RequireStableFooting: true,
|
||||
RequireGrounded: true,
|
||||
RequireJumpReady: false,
|
||||
AllowAirBrake: true,
|
||||
HorizonTicks: 12),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = new Location(1.5, 81, 1.5),
|
||||
End = new Location(2.5, 81, 1.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new AscendTemplate(ascend, next);
|
||||
|
||||
// Seed mid-arc state mirroring the live PathDiag tick-trace at the
|
||||
// first observed tick of seg9/41: bot already airborne with rising
|
||||
// vy, cardinal +Z momentum from the preceding Descend, and yaw
|
||||
// pointing roughly NE (the AscendTemplate snapped yaw on the
|
||||
// takeoff tick).
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(0.7, 80.42, 0.92),
|
||||
DeltaMovement = new Vec3d(0.0, 0.333, 0.145),
|
||||
OnGround = false,
|
||||
Sprinting = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = 311f,
|
||||
Pitch = 0f
|
||||
};
|
||||
|
||||
var input = new MovementInput();
|
||||
TemplateState state = TemplateState.InProgress;
|
||||
Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
var trace = new List<string>();
|
||||
int elapsedTicks = 0;
|
||||
for (; elapsedTicks < 80; elapsedTicks++)
|
||||
{
|
||||
input.Reset();
|
||||
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
state = template.Tick(pos, physics, input, world);
|
||||
if (elapsedTicks < 30 || state != TemplateState.InProgress)
|
||||
{
|
||||
trace.Add(
|
||||
$"tick={elapsedTicks} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " +
|
||||
$"onGround={physics.OnGround} input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})");
|
||||
}
|
||||
if (state != TemplateState.InProgress)
|
||||
{
|
||||
finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
break;
|
||||
}
|
||||
|
||||
physics.ApplyInput(input);
|
||||
physics.Tick(world);
|
||||
finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
}
|
||||
|
||||
Assert.True(
|
||||
state == TemplateState.Complete,
|
||||
$"state={state} elapsed={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}");
|
||||
Assert.True(
|
||||
physics.OnGround,
|
||||
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement}");
|
||||
Assert.True(
|
||||
TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, ascend.End),
|
||||
$"finalPos={finalPos} target={ascend.End}\n{string.Join('\n', trace)}");
|
||||
Assert.True(
|
||||
elapsedTicks <= 30,
|
||||
$"completion took {elapsedTicks} ticks; expected post-landing shortcut to fire promptly\n{string.Join('\n', trace)}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
private Location _lastPos;
|
||||
private int _stuckTicks;
|
||||
private bool _initiatedJump;
|
||||
private bool _hasBeenAirborne;
|
||||
private int _diagonalBrakeTicks;
|
||||
|
||||
public AscendTemplate(PathSegment segment, PathSegment? nextSegment)
|
||||
|
|
@ -182,8 +183,38 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
}
|
||||
}
|
||||
|
||||
if (!physics.OnGround)
|
||||
_hasBeenAirborne = true;
|
||||
|
||||
if (physics.OnGround && Math.Abs(dy) < 0.2)
|
||||
{
|
||||
// Post-landing shortcut: once the Ascend's jump arc has put the
|
||||
// bot back on ground at the target's elevation with its center
|
||||
// inside the target column, the segment has done its job. Hand
|
||||
// off to the next template (which snaps yaw on its first tick)
|
||||
// instead of trying to brake or settle to stable footing.
|
||||
//
|
||||
// Holding onto the segment here re-runs both the AscendTemplate
|
||||
// top-level yaw smoothing toward targetYaw (a moving bearing as
|
||||
// the bot drifts past End) AND GroundedSegmentController's
|
||||
// segment/exit-heading rotation each tick. The two competing
|
||||
// yaw targets (e.g. yaw=233 anti-velocity vs yaw=315 segment
|
||||
// heading vs yaw=270 exit heading on a Descend->PrepareJump->
|
||||
// Ascend->Turn chain) oscillate the bot ~80 ticks until it
|
||||
// walks off the 1-block landing's edge and the segment fails.
|
||||
// Mirrors the existing "Ascend completes on PrepareJump as
|
||||
// soon as center is inside the target block" gate in
|
||||
// GroundedSegmentController.ShouldComplete. FinalStop is
|
||||
// excluded because the last segment must come to rest at
|
||||
// the goal: hand it back to GroundedSegmentController,
|
||||
// which uses IsSettledAtEnd to detect a true stop.
|
||||
if (_hasBeenAirborne
|
||||
&& _segment.ExitTransition != PathTransitionType.FinalStop
|
||||
&& TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End))
|
||||
{
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
|
|
|
|||
|
|
@ -150,9 +150,31 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
&& (onOrPastTarget
|
||||
|| (_hasFallen
|
||||
&& TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment, distanceThreshold: 1.5))));
|
||||
float airborneYaw = biasTowardExitInAir
|
||||
? TemplateHelper.GetExitHeadingYaw(_segment)
|
||||
: targetYaw;
|
||||
float airborneYaw;
|
||||
if (biasTowardExitInAir)
|
||||
{
|
||||
airborneYaw = TemplateHelper.GetExitHeadingYaw(_segment);
|
||||
}
|
||||
else if (!isSingleStepDescend)
|
||||
{
|
||||
// Multi-block descend: target-tracking yaw rotates as
|
||||
// the bot drifts past the landing column mid-fall (e.g.
|
||||
// a diagonal 3 c2c drop with dx=-1, dz=-1 starts at
|
||||
// yaw=135, the relative bearing to End flips through
|
||||
// 90 -> 0 -> 315 in 6 air ticks). With Forward input
|
||||
// held, the rotating yaw pushes air-control momentum
|
||||
// perpendicular to the planned trajectory, drifting
|
||||
// the bot ~0.5 m past the landing column and onto an
|
||||
// adjacent block one tier below. Lock airborne yaw to
|
||||
// the segment's start->end heading so air drift stays
|
||||
// aligned with the planned diagonal; the GroundedSegment
|
||||
// controller takes over once the bot is on the landing.
|
||||
airborneYaw = TemplateHelper.CalculateYaw(_segment.HeadingX, _segment.HeadingZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
airborneYaw = targetYaw;
|
||||
}
|
||||
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, airborneYaw);
|
||||
if (_hasFallen || YawDifference(physics.Yaw, airborneYaw) <= PreDropYawToleranceDeg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue