Minecraft-Console-Client/MinecraftClient/Pathing/Execution/PathExecutor.cs
BruceChen 95b20d9d1c pathing: async replan + template success/failure alignment
Move PathSegmentManager's Replan to Task.Run so the main tick only reads
results and swaps executors, and introduce a _nextExecutor pre-planning
slot so upcoming segments can prepare while the current one finishes.

Relax per-tick yaw/pitch rate limiting: allow instantaneous snapping
before jump ticks (Baritone does this and servers do not kick for it).

Align jump-template success/failure contracts with Baritone:
- Success key shifts from "speed squared" to "feet-on-target block".
- Failure window widened to the ~200 tick range.
- AscendTemplate gets a headBonkClear + edge/side proximity
  precondition so launches only happen from a safe takeoff.

Expose an initialMomentumTicks option on TemplateSimulationRunner so
follow-up sidewall scenarios can warm up physics before a template
starts.

Made-with: Cursor
2026-04-19 17:02:41 +00:00

126 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using MinecraftClient.Mapping;
using MinecraftClient.Pathing.Execution.Telemetry;
using MinecraftClient.Physics;
namespace MinecraftClient.Pathing.Execution
{
public enum PathExecutorState
{
InProgress,
Failed,
Complete
}
/// <summary>
/// Drives a sequence of PathSegments by instantiating the correct IActionTemplate
/// for each segment and ticking it every game tick.
/// </summary>
public sealed class PathExecutor
{
private readonly List<PathSegment> _segments;
private int _currentIndex;
private IActionTemplate? _currentTemplate;
private readonly Action<string>? _debugLog;
private readonly IPathExecutionObserver? _observer;
private int _segmentTicks;
private int _totalTicks;
public bool IsComplete => _currentIndex >= _segments.Count && _currentTemplate is null;
public int CurrentIndex => _currentIndex;
public int TotalSegments => _segments.Count;
public int TotalTicks => _totalTicks;
public PathSegment? CurrentSegment =>
_currentIndex < _segments.Count ? _segments[_currentIndex] : null;
public PathSegment? LastSegment =>
_segments.Count > 0 ? _segments[^1] : null;
public PathExecutor(List<PathSegment> segments, Action<string>? debugLog = null, IPathExecutionObserver? observer = null)
{
_segments = segments;
_currentIndex = 0;
_debugLog = debugLog;
_observer = observer;
_observer?.OnNavigationStarted(segments);
AdvanceToNextSegment();
}
public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world)
{
if (_currentTemplate is null)
{
input.Reset();
return PathExecutorState.Complete;
}
_totalTicks++;
int sameTickAdvanceCount = 0;
while (_currentTemplate is not null)
{
_segmentTicks++;
var state = _currentTemplate.Tick(pos, physics, input, world);
switch (state)
{
case TemplateState.Complete:
_observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " +
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})");
_currentIndex++;
_segmentTicks = 0;
if (_currentIndex >= _segments.Count)
{
input.Reset();
_currentTemplate = null;
_debugLog?.Invoke("[PathExec] All segments complete!");
return PathExecutorState.Complete;
}
AdvanceToNextSegment();
// Do not waste the handoff tick when the next segment needs to issue
// a jump or braking input immediately.
sameTickAdvanceCount++;
if (sameTickAdvanceCount > _segments.Count)
{
input.Reset();
_debugLog?.Invoke("[PathExec] Excessive same-tick segment advances; aborting.");
return PathExecutorState.Failed;
}
continue;
case TemplateState.Failed:
input.Reset();
_observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " +
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " +
$"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})");
return PathExecutorState.Failed;
default:
return PathExecutorState.InProgress;
}
}
input.Reset();
return PathExecutorState.Complete;
}
private void AdvanceToNextSegment()
{
if (_currentIndex < _segments.Count)
{
var seg = _segments[_currentIndex];
PathSegment? next = _currentIndex + 1 < _segments.Count ? _segments[_currentIndex + 1] : null;
_currentTemplate = ActionTemplateFactory.Create(seg, next);
_observer?.OnSegmentStarted(_currentIndex, _segments.Count, seg);
_debugLog?.Invoke($"[PathExec] Starting segment {_currentIndex}/{_segments.Count}: {seg}");
}
else
{
_currentTemplate = null;
}
}
}
}