mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
pathing: align parkour contracts with live budgets
This commit is contained in:
parent
ce555da95e
commit
cf8bf349db
16 changed files with 3190 additions and 318 deletions
|
|
@ -150,6 +150,8 @@ public sealed class PathingContractStore
|
|||
throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' must use zero totals when it has no segments.");
|
||||
|
||||
var normalizedSegments = new List<PathingSegmentTimingBudget>(budget.Segments.Count);
|
||||
int expectedSegmentTicksSum = 0;
|
||||
int maxSegmentTicksSum = 0;
|
||||
for (int i = 0; i < budget.Segments.Count; i++)
|
||||
{
|
||||
PathingSegmentTimingBudget segment = budget.Segments[i];
|
||||
|
|
@ -159,9 +161,23 @@ public sealed class PathingContractStore
|
|||
if (segment.ExpectedTicks > segment.MaxTicks)
|
||||
throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' segment {i} has ExpectedTicks greater than MaxTicks.");
|
||||
|
||||
expectedSegmentTicksSum = checked(expectedSegmentTicksSum + segment.ExpectedTicks);
|
||||
maxSegmentTicksSum = checked(maxSegmentTicksSum + segment.MaxTicks);
|
||||
normalizedSegments.Add(segment);
|
||||
}
|
||||
|
||||
if (budget.ExpectedTotalTicks != expectedSegmentTicksSum)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Timing budget '{budget.ScenarioId}' ExpectedTotalTicks mismatch. Total={budget.ExpectedTotalTicks}, segmentSum={expectedSegmentTicksSum}.");
|
||||
}
|
||||
|
||||
if (budget.MaxTotalTicks != maxSegmentTicksSum)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Timing budget '{budget.ScenarioId}' MaxTotalTicks mismatch. Total={budget.MaxTotalTicks}, segmentSum={maxSegmentTicksSum}.");
|
||||
}
|
||||
|
||||
return budget with { Segments = normalizedSegments.AsReadOnly() };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using MinecraftClient.Pathing.Core;
|
|||
using MinecraftClient.Pathing.Execution;
|
||||
using MinecraftClient.Pathing.Execution.Templates;
|
||||
using MinecraftClient.Pathing.Goals;
|
||||
using MinecraftClient.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
|
@ -35,6 +36,94 @@ public sealed class LivePathingRegressionTests
|
|||
Assert.Empty(PathSegmentBuilder.FromPath(result.Path));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AStar_RepeatedSingleGapParkourChain_PrefersTwoLongJumpsOverFourShortJumps()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 578, max: 590);
|
||||
FlatWorldTestBuilder.ClearBox(world, 578, 79, 578, 590, 90, 582);
|
||||
FlatWorldTestBuilder.SetSolid(world, 580, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 582, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 584, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 586, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 588, 79, 580);
|
||||
|
||||
var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true);
|
||||
var finder = new AStarPathFinder();
|
||||
|
||||
PathResult result = finder.Calculate(
|
||||
ctx,
|
||||
startX: 580,
|
||||
startY: 80,
|
||||
startZ: 580,
|
||||
new GoalBlock(588, 80, 580),
|
||||
CancellationToken.None,
|
||||
timeoutMs: 2000);
|
||||
|
||||
List<PathSegment> segments = PathSegmentBuilder.FromPath(result.Path);
|
||||
|
||||
Assert.Equal(PathStatus.Success, result.Status);
|
||||
Assert.Collection(
|
||||
segments,
|
||||
first =>
|
||||
{
|
||||
Assert.Equal(MoveType.Parkour, first.MoveType);
|
||||
Assert.Equal(new Location(580.5, 80, 580.5), first.Start);
|
||||
Assert.Equal(new Location(584.5, 80, 580.5), first.End);
|
||||
},
|
||||
second =>
|
||||
{
|
||||
Assert.Equal(MoveType.Parkour, second.MoveType);
|
||||
Assert.Equal(new Location(584.5, 80, 580.5), second.Start);
|
||||
Assert.Equal(new Location(588.5, 80, 580.5), second.End);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PathExecutor_RepeatedSingleGapParkourChain_TwoLongJumps_CompletesWithoutReplan()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 578, max: 590);
|
||||
FlatWorldTestBuilder.ClearBox(world, 578, 79, 578, 590, 90, 582);
|
||||
FlatWorldTestBuilder.SetSolid(world, 580, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 582, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 584, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 586, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 588, 79, 580);
|
||||
|
||||
var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true);
|
||||
var finder = new AStarPathFinder();
|
||||
PathResult result = finder.Calculate(
|
||||
ctx,
|
||||
startX: 580,
|
||||
startY: 80,
|
||||
startZ: 580,
|
||||
new GoalBlock(588, 80, 580),
|
||||
CancellationToken.None,
|
||||
timeoutMs: 2000);
|
||||
|
||||
var debugLogs = new List<string>();
|
||||
var infoLogs = new List<string>();
|
||||
var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(580.5, 80, 580.5), yaw: 270f);
|
||||
var input = new MovementInput();
|
||||
|
||||
manager.StartNavigation(new GoalBlock(588, 80, 580), result);
|
||||
|
||||
for (int tick = 0; tick < 240 && manager.IsNavigating; tick++)
|
||||
{
|
||||
input.Reset();
|
||||
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
manager.Tick(pos, physics, input, world);
|
||||
if (!manager.IsNavigating)
|
||||
break;
|
||||
|
||||
physics.ApplyInput(input);
|
||||
physics.Tick(world);
|
||||
}
|
||||
|
||||
Assert.True(!manager.IsNavigating && manager.ReplanCount == 0,
|
||||
$"replanCount={manager.ReplanCount}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SprintJumpTemplate_LandingRecoveryIntoTurn_CompletesInsideLandingBlock()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -86,6 +86,49 @@ public sealed class PathPlanningContractTests
|
|||
Assert.Contains("manager-accepted-ascend-chain", error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_RejectsTimingBudget_WhenTotalsDoNotMatchSegments()
|
||||
{
|
||||
const string plannerJson = """
|
||||
[
|
||||
{
|
||||
"scenarioId": "totals-mismatch",
|
||||
"expectedStatus": "Success",
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"startBlock": { "x": 0, "y": 80, "z": 0 },
|
||||
"endBlock": { "x": 1, "y": 80, "z": 0 }
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"startBlock": { "x": 1, "y": 80, "z": 0 },
|
||||
"endBlock": { "x": 2, "y": 81, "z": 0 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
""";
|
||||
const string timingJson = """
|
||||
[
|
||||
{
|
||||
"scenarioId": "totals-mismatch",
|
||||
"expectedTotalTicks": 1,
|
||||
"maxTotalTicks": 2,
|
||||
"segments": [
|
||||
{ "moveType": "Traverse", "expectedTicks": 2, "maxTicks": 3 },
|
||||
{ "moveType": "Ascend", "expectedTicks": 3, "maxTicks": 4 }
|
||||
]
|
||||
}
|
||||
]
|
||||
""";
|
||||
|
||||
InvalidDataException error = Assert.Throws<InvalidDataException>(
|
||||
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
|
||||
Assert.Contains("totals-mismatch", error.Message);
|
||||
Assert.Contains("ExpectedTotalTicks mismatch", error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_Rejects_WhenPlannerAndTimingScenarioSetsMismatch()
|
||||
{
|
||||
|
|
@ -214,25 +257,26 @@ public sealed class PathPlanningContractTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("manager-accepted-ascend-chain")]
|
||||
[InlineData("same-move-ascend-staircase")]
|
||||
[InlineData("same-move-descend-staircase")]
|
||||
[InlineData("rejected-3x1-invalid-goal")]
|
||||
public void Scenario_PlannerMatchesContract(string scenarioId)
|
||||
{
|
||||
PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId);
|
||||
PathResult planResult = PathingScenarioRunner.PlanOnly(scenario);
|
||||
PathingPlannerContract contract = PathingContractStore.LoadFromRepositoryRoot().GetPlanner(scenarioId);
|
||||
|
||||
PathingContractAssert.PlannerMatches(contract, PathSegmentBuilder.FromPath(planResult.Path), planResult);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("repeated-cardinal-parkour-chain")]
|
||||
[InlineData("repeated-diagonal-parkour-chain")]
|
||||
[InlineData("obstructed-parkour-l-turns")]
|
||||
[InlineData("vertical-jump-mix")]
|
||||
[InlineData("diagonal-vertical-mix")]
|
||||
public void JumpCombo_PlannerMatchesContract(string scenarioId)
|
||||
[InlineData("turn-density-alternating-traverse-diagonal-chain")]
|
||||
[InlineData("mixed-traverse-ascend-parkour-descend")]
|
||||
[InlineData("same-move-aligned-parkour-chain")]
|
||||
[InlineData("mixed-diagonal-ascend-traverse-descend")]
|
||||
[InlineData("speed-carry-repeated-traverse-ascend")]
|
||||
[InlineData("speed-carry-repeated-traverse-descend")]
|
||||
[InlineData("speed-carry-repeated-traverse-parkour")]
|
||||
[InlineData("same-move-diagonal-chain")]
|
||||
[InlineData("same-move-straight-traverse-chain")]
|
||||
[InlineData("mixed-traverse-turn-parkour-turn-traverse")]
|
||||
public void Scenario_PlannerMatchesContract(string scenarioId)
|
||||
{
|
||||
PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId);
|
||||
PathResult planResult = PathingScenarioRunner.PlanOnly(scenario);
|
||||
|
|
|
|||
|
|
@ -62,9 +62,28 @@ internal static class PathingContractAssert
|
|||
sb.AppendLine($"seg[{i}] move={actual.MoveType} actual={actual.ElapsedTicks} expected={expected.ExpectedTicks} max={expected.MaxTicks}");
|
||||
}
|
||||
|
||||
if (result.InfoLogs.Count > 0)
|
||||
{
|
||||
sb.AppendLine("info tail:");
|
||||
AppendTail(sb, result.InfoLogs, maxLines: 8);
|
||||
}
|
||||
|
||||
if (result.DebugLogs.Count > 0)
|
||||
{
|
||||
sb.AppendLine("debug tail:");
|
||||
AppendTail(sb, result.DebugLogs, maxLines: 12);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void AppendTail(StringBuilder sb, IReadOnlyList<string> lines, int maxLines)
|
||||
{
|
||||
int start = Math.Max(0, lines.Count - maxLines);
|
||||
for (int i = start; i < lines.Count; i++)
|
||||
sb.AppendLine(lines[i]);
|
||||
}
|
||||
|
||||
private static PathingBlock ToBlock(Location location) =>
|
||||
new((int)Math.Floor(location.X), (int)Math.Floor(location.Y), (int)Math.Floor(location.Z));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,26 @@ public sealed class MoveParkourTests
|
|||
Assert.Equal(2, result.DestX);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepts4x1JumpWithoutRearSupport_WhenTakeoffBlockProvidesRunway()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 6);
|
||||
FlatWorldTestBuilder.ClearBox(world, -2, FloorY, -1, 6, FloorY + 4, 1);
|
||||
FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0);
|
||||
FlatWorldTestBuilder.SetSolid(world, 2, FloorY, 0);
|
||||
FlatWorldTestBuilder.SetSolid(world, 4, FloorY, 0);
|
||||
|
||||
var ctx = BuildContext(world);
|
||||
var move = new MoveParkour(4, 0);
|
||||
var result = default(MoveResult);
|
||||
|
||||
move.Calculate(ctx, 0, FloorY + 1, 0, ref result);
|
||||
|
||||
Assert.False(result.IsImpossible);
|
||||
Assert.Equal(4, result.DestX);
|
||||
Assert.Equal(0, result.DestZ);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rejects2x1WhenAdjacentBlockIsStillWalkable()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -215,19 +215,6 @@
|
|||
"y": 80,
|
||||
"z": 580
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 582,
|
||||
"y": 80,
|
||||
"z": 580
|
||||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 582,
|
||||
"y": 80,
|
||||
"z": 580
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 584,
|
||||
"y": 80,
|
||||
|
|
@ -241,19 +228,6 @@
|
|||
"y": 80,
|
||||
"z": 580
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 586,
|
||||
"y": 80,
|
||||
"z": 580
|
||||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 586,
|
||||
"y": 80,
|
||||
"z": 580
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 588,
|
||||
"y": 80,
|
||||
|
|
@ -370,25 +344,12 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 642,
|
||||
"y": 81,
|
||||
"z": 620
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 644,
|
||||
"y": 80,
|
||||
"z": 620
|
||||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 644,
|
||||
"y": 80,
|
||||
"z": 620
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 646,
|
||||
"y": 81,
|
||||
|
|
@ -634,19 +595,6 @@
|
|||
"y": 80,
|
||||
"z": 380
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 382,
|
||||
"y": 80,
|
||||
"z": 380
|
||||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 382,
|
||||
"y": 80,
|
||||
"z": 380
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 384,
|
||||
"y": 80,
|
||||
|
|
@ -660,19 +608,6 @@
|
|||
"y": 80,
|
||||
"z": 380
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 386,
|
||||
"y": 80,
|
||||
"z": 380
|
||||
}
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"startBlock": {
|
||||
"x": 386,
|
||||
"y": 80,
|
||||
"z": 380
|
||||
},
|
||||
"endBlock": {
|
||||
"x": 388,
|
||||
"y": 80,
|
||||
|
|
|
|||
|
|
@ -38,55 +38,55 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "same-move-ascend-staircase",
|
||||
"expectedTotalTicks": 56,
|
||||
"maxTotalTicks": 68,
|
||||
"expectedTotalTicks": 60,
|
||||
"maxTotalTicks": 74,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
"expectedTicks": 10,
|
||||
"maxTicks": 12
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "same-move-descend-staircase",
|
||||
"expectedTotalTicks": 61,
|
||||
"maxTotalTicks": 74,
|
||||
"expectedTotalTicks": 57,
|
||||
"maxTotalTicks": 70,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 24,
|
||||
"maxTicks": 29
|
||||
"expectedTicks": 23,
|
||||
"maxTicks": 28
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 25,
|
||||
"maxTicks": 30
|
||||
"expectedTicks": 23,
|
||||
"maxTicks": 28
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -98,79 +98,47 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "repeated-cardinal-parkour-chain",
|
||||
"expectedTotalTicks": 0,
|
||||
"maxTotalTicks": 2,
|
||||
"expectedTotalTicks": 37,
|
||||
"maxTotalTicks": 45,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
"expectedTicks": 18,
|
||||
"maxTicks": 22
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 27,
|
||||
"maxTicks": 33
|
||||
"expectedTicks": 19,
|
||||
"maxTicks": 23
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "repeated-diagonal-parkour-chain",
|
||||
"expectedTotalTicks": 20,
|
||||
"maxTotalTicks": 24,
|
||||
"expectedTotalTicks": 67,
|
||||
"maxTotalTicks": 82,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
"expectedTicks": 36,
|
||||
"maxTicks": 44
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 20,
|
||||
"maxTicks": 24
|
||||
"expectedTicks": 17,
|
||||
"maxTicks": 21
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "obstructed-parkour-l-turns",
|
||||
"expectedTotalTicks": 0,
|
||||
"maxTotalTicks": 2,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 27,
|
||||
"maxTicks": 33
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 27,
|
||||
"maxTicks": 33
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 27,
|
||||
"maxTicks": 33
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "vertical-jump-mix",
|
||||
"expectedTotalTicks": 33,
|
||||
"maxTotalTicks": 40,
|
||||
"expectedTotalTicks": 50,
|
||||
"maxTotalTicks": 62,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
|
|
@ -178,14 +146,53 @@
|
|||
"maxTicks": 16
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 201,
|
||||
"maxTicks": 242
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 21,
|
||||
"maxTicks": 26
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 19,
|
||||
"maxTicks": 23
|
||||
"expectedTicks": 16,
|
||||
"maxTicks": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "vertical-jump-mix",
|
||||
"expectedTotalTicks": 41,
|
||||
"maxTotalTicks": 50,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 10,
|
||||
"maxTicks": 12
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 18,
|
||||
"maxTicks": 22
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 13,
|
||||
"maxTicks": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "diagonal-vertical-mix",
|
||||
"expectedTotalTicks": 38,
|
||||
"maxTotalTicks": 46,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 10,
|
||||
"maxTicks": 12
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
|
|
@ -194,32 +201,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "diagonal-vertical-mix",
|
||||
"expectedTotalTicks": 31,
|
||||
"maxTotalTicks": 38,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 16,
|
||||
"maxTicks": 20
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 15,
|
||||
"maxTicks": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "turn-density-alternating-traverse-diagonal-chain",
|
||||
"expectedTotalTicks": 47,
|
||||
"maxTotalTicks": 57,
|
||||
"maxTotalTicks": 59,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Diagonal",
|
||||
|
|
@ -255,72 +240,62 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "mixed-traverse-ascend-parkour-descend",
|
||||
"expectedTotalTicks": 40,
|
||||
"maxTotalTicks": 48,
|
||||
"expectedTotalTicks": 70,
|
||||
"maxTotalTicks": 88,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 6,
|
||||
"maxTicks": 8
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
"expectedTicks": 13,
|
||||
"maxTicks": 16
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 21,
|
||||
"maxTicks": 26
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "same-move-aligned-parkour-chain",
|
||||
"expectedTotalTicks": 37,
|
||||
"maxTotalTicks": 45,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 18,
|
||||
"maxTicks": 22
|
||||
},
|
||||
{
|
||||
"moveType": "Descend",
|
||||
"expectedTicks": 22,
|
||||
"maxTicks": 27
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "same-move-aligned-parkour-chain",
|
||||
"expectedTotalTicks": 0,
|
||||
"maxTotalTicks": 2,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 61,
|
||||
"maxTicks": 74
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 27,
|
||||
"maxTicks": 33
|
||||
"expectedTicks": 19,
|
||||
"maxTicks": 23
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "mixed-diagonal-ascend-traverse-descend",
|
||||
"expectedTotalTicks": 96,
|
||||
"maxTotalTicks": 116,
|
||||
"expectedTotalTicks": 76,
|
||||
"maxTotalTicks": 95,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Diagonal",
|
||||
|
|
@ -334,13 +309,13 @@
|
|||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 32,
|
||||
"maxTicks": 39
|
||||
"expectedTicks": 10,
|
||||
"maxTicks": 12
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
"expectedTicks": 13,
|
||||
"maxTicks": 16
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
|
|
@ -361,19 +336,9 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "speed-carry-repeated-traverse-ascend",
|
||||
"expectedTotalTicks": 66,
|
||||
"maxTotalTicks": 80,
|
||||
"expectedTotalTicks": 70,
|
||||
"maxTotalTicks": 90,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 6,
|
||||
"maxTicks": 8
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 5,
|
||||
|
|
@ -381,18 +346,8 @@
|
|||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 11,
|
||||
"maxTicks": 14
|
||||
"expectedTicks": 13,
|
||||
"maxTicks": 16
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
|
|
@ -403,23 +358,43 @@
|
|||
"moveType": "Ascend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 12,
|
||||
"maxTicks": 15
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Ascend",
|
||||
"expectedTicks": 13,
|
||||
"maxTicks": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "speed-carry-repeated-traverse-descend",
|
||||
"expectedTotalTicks": 41,
|
||||
"maxTotalTicks": 50,
|
||||
"expectedTotalTicks": 45,
|
||||
"maxTotalTicks": 57,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 22,
|
||||
"maxTicks": 27
|
||||
"expectedTicks": 21,
|
||||
"maxTicks": 26
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
|
|
@ -435,45 +410,45 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "speed-carry-repeated-traverse-parkour",
|
||||
"expectedTotalTicks": 0,
|
||||
"maxTotalTicks": 2,
|
||||
"expectedTotalTicks": 58,
|
||||
"maxTotalTicks": 73,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 18,
|
||||
"maxTicks": 22
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 14,
|
||||
"maxTicks": 17
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 5,
|
||||
"maxTicks": 7
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 15,
|
||||
"maxTicks": 18
|
||||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
"expectedTicks": 29,
|
||||
"maxTicks": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenarioId": "same-move-diagonal-chain",
|
||||
"expectedTotalTicks": 55,
|
||||
"maxTotalTicks": 66,
|
||||
"maxTotalTicks": 69,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Diagonal",
|
||||
|
|
@ -515,7 +490,7 @@
|
|||
{
|
||||
"scenarioId": "same-move-straight-traverse-chain",
|
||||
"expectedTotalTicks": 70,
|
||||
"maxTotalTicks": 84,
|
||||
"maxTotalTicks": 94,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
|
|
@ -581,8 +556,8 @@
|
|||
},
|
||||
{
|
||||
"scenarioId": "mixed-traverse-turn-parkour-turn-traverse",
|
||||
"expectedTotalTicks": 46,
|
||||
"maxTotalTicks": 56,
|
||||
"expectedTotalTicks": 60,
|
||||
"maxTotalTicks": 75,
|
||||
"segments": [
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
|
|
@ -591,8 +566,8 @@
|
|||
},
|
||||
{
|
||||
"moveType": "Diagonal",
|
||||
"expectedTicks": 81,
|
||||
"maxTicks": 98
|
||||
"expectedTicks": 6,
|
||||
"maxTicks": 8
|
||||
},
|
||||
{
|
||||
"moveType": "Parkour",
|
||||
|
|
@ -601,8 +576,8 @@
|
|||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 7,
|
||||
"maxTicks": 9
|
||||
"expectedTicks": 8,
|
||||
"maxTicks": 10
|
||||
},
|
||||
{
|
||||
"moveType": "Diagonal",
|
||||
|
|
@ -616,8 +591,8 @@
|
|||
},
|
||||
{
|
||||
"moveType": "Traverse",
|
||||
"expectedTicks": 8,
|
||||
"maxTicks": 10
|
||||
"expectedTicks": 7,
|
||||
"maxTicks": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ internal static class ParkourFeasibility
|
|||
int yDelta)
|
||||
{
|
||||
double horiz = Math.Sqrt(xOffset * xOffset + zOffset * zOffset);
|
||||
double threshold = yDelta > 0 ? 2.5 : 3.5;
|
||||
if (yDelta <= 0)
|
||||
return true;
|
||||
|
||||
double threshold = 2.5;
|
||||
if (horiz < threshold)
|
||||
return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,8 @@ For rejection scenarios, the requirement is stricter:
|
|||
|
||||
Residual speed carried from one movement to the next inside a route is expected and must not be normalized away just to satisfy the harness. The route is only considered reliable if that natural speed carry still produces `0 replan`.
|
||||
|
||||
Independent live-route cases must reset position, yaw, and pitch to the scenario start state before each run. Cross-case orientation residue is harness noise, not valid pathing difficulty.
|
||||
|
||||
## Baritone Reference Notes For Zero-Replan Work
|
||||
|
||||
MCC can borrow specific ideas from the local Baritone reference under `ThirdpartyReference/baritone/`, but not its looser success semantics.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,877 @@
|
|||
# Pathing Execution Regression Fixes Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Remove the current execution-layer regressions exposed by the contract/timing harness so deterministic jump-combo and long-route scenarios complete with `0` replans and within their existing budgets.
|
||||
|
||||
**Architecture:** Treat the failures as three runtime bugs, not as harness problems. First tighten parkour landing recovery so chained jumps hand off with the right speed instead of stalling or replan-looping. Second make transition braking and lookahead score the next segment entry contract, so mixed turn/ascend/descend routes stop choosing the wrong carry-or-brake profile. Third harden chained ascends for live-runtime carry states so staircases stop burning extra ticks after each landing. Keep the existing JSON contracts, scenario catalog, and shell harnesses unchanged except for verification.
|
||||
|
||||
**Tech Stack:** C# 14 / .NET 10, xUnit, MCC pathing execution templates, `PlayerPhysics`, existing `MinecraftClient.Tests` scenario runner and timing contracts, local `1.21.11-Vanilla` live harness via `tools/mcc-env.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Scope Check
|
||||
|
||||
This plan only covers runtime execution fixes in the existing pathing stack.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- planner-contract schema changes
|
||||
- theory-matrix generation changes
|
||||
- telemetry/report format changes
|
||||
- new live harness features
|
||||
- broad planner heuristics refactors
|
||||
|
||||
## Current Failure Inventory
|
||||
|
||||
Focused xUnit evidence from:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal
|
||||
```
|
||||
|
||||
Current failing families:
|
||||
|
||||
- repeated parkour chains do not complete cleanly
|
||||
- `repeated-cardinal-parkour-chain`: navigation did not complete, `replans=4`
|
||||
- `repeated-diagonal-parkour-chain`: expected `0` replans, saw `2`
|
||||
- `obstructed-parkour-l-turns`: navigation did not complete, `replans=1`
|
||||
- `same-move-aligned-parkour-chain`: navigation did not complete, `replans=4`
|
||||
- mixed vertical and mixed long routes over-brake or replan unexpectedly
|
||||
- `vertical-jump-mix`: expected `0` replans, saw `1`
|
||||
- `diagonal-vertical-mix`: expected `0` replans, saw `1`
|
||||
- `mixed-traverse-turn-parkour-turn-traverse`: expected `0` replans, saw `1`
|
||||
- `mixed-traverse-ascend-parkour-descend`: expected `0` replans, saw `1`
|
||||
- `speed-carry-repeated-traverse-descend`: expected `0` replans, saw `1`
|
||||
- `speed-carry-repeated-traverse-parkour`: navigation did not complete, `replans=4`
|
||||
|
||||
Live harness evidence:
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-jump-combos.sh 1.21.11-Vanilla
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Current live failures:
|
||||
|
||||
- `same-move-ascend-staircase`: `actual=145 max=68`, first four ascend segments each over by roughly `+22` to `+23` ticks
|
||||
- `vertical-jump-mix`: `actual=54 max=40`
|
||||
- repeated parkour chains fail with segment failure followed by replan loops
|
||||
|
||||
## Problem Map
|
||||
|
||||
1. `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`
|
||||
- landing recovery is still biased toward “settle fully” behavior
|
||||
- `pastTarget` release is too blunt for repeated parkour and mixed jump chains
|
||||
- completion rules do not preserve enough entry speed for immediate follow-up jumps
|
||||
|
||||
2. `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs`
|
||||
- planning still reasons mostly about the current segment
|
||||
- special-cases landing-recovery turns, but not the broader mixed-route handoff problem
|
||||
|
||||
3. `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs`
|
||||
- air and ground scoring ignore too much next-segment intent
|
||||
- current profiles cannot distinguish “slow down for stable turn entry” from “keep enough speed for the next descend or jump”
|
||||
|
||||
4. `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- chained ascends do not explicitly separate takeoff, airborne, and landing handoff
|
||||
- live staircase traces show repeated post-landing delay before the next step starts
|
||||
|
||||
5. `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- grounded completion is strong for final stops, but too conservative for continue-straight ascend handoff
|
||||
|
||||
## File Structure
|
||||
|
||||
### Production files
|
||||
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`
|
||||
- parkour landing recovery completion and in-air release rules
|
||||
- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs`
|
||||
- next-segment-aware braking decisions
|
||||
- Modify: `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs`
|
||||
- ground and air profile scoring that considers the next segment contract
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- explicit ascend phase handling and faster landing handoff
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- shared completion rules for continue-straight ascend chaining
|
||||
|
||||
### Test files
|
||||
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`
|
||||
- named regression entry points for representative failing scenarios
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- deterministic chained-jump handoff regression
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs`
|
||||
- ground and air next-segment profile regressions
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs`
|
||||
- planner decisions for mixed handoff states
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- chained-ascend convergence regression
|
||||
|
||||
### Verification only
|
||||
|
||||
- Reuse: `tools/test-pathing-jump-combos.sh`
|
||||
- Reuse: `tools/test-pathing-long-routes.sh`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Stabilize Repeated Parkour Landing Recovery
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`
|
||||
|
||||
- [ ] **Step 1: Write failing parkour-focused regression tests**
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`:
|
||||
|
||||
```csharp
|
||||
using MinecraftClient.Tests.Pathing.Execution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
public sealed class PathTimingContractTests
|
||||
{
|
||||
[Fact]
|
||||
public void RepeatedCardinalParkourChain_ExecutionStaysWithinBudget() =>
|
||||
AssertScenarioWithinBudget("repeated-cardinal-parkour-chain");
|
||||
|
||||
[Fact]
|
||||
public void RepeatedDiagonalParkourChain_ExecutionStaysWithinBudget() =>
|
||||
AssertScenarioWithinBudget("repeated-diagonal-parkour-chain");
|
||||
|
||||
private static void AssertScenarioWithinBudget(string scenarioId)
|
||||
{
|
||||
PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId);
|
||||
PathingTimingBudget budget = PathingContractStore.LoadFromRepositoryRoot().GetTiming(scenarioId);
|
||||
PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario);
|
||||
|
||||
PathingContractAssert.TimingMatches(budget, result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void SprintJumpTemplate_LandingRecovery_LeavesEnoughSpeedForNextParkour()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 578, max: 586);
|
||||
FlatWorldTestBuilder.ClearBox(world, 578, 79, 578, 586, 90, 582);
|
||||
FlatWorldTestBuilder.SetSolid(world, 580, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 582, 79, 580);
|
||||
FlatWorldTestBuilder.SetSolid(world, 584, 79, 580);
|
||||
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(580.5, 80, 580.5),
|
||||
End = new Location(582.5, 80, 580.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.LandingRecovery,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.12, 0.20, false, true, true, true, 12),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = current.End,
|
||||
End = new Location(584.5, 80, 580.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.LandingRecovery,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.12, 0.20, false, true, true, true, 12),
|
||||
PreserveSprint = true
|
||||
};
|
||||
|
||||
var template = new SprintJumpTemplate(current, next);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 270f);
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos);
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
Assert.True(TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, current.End), $"finalPos={finalPos} vel={physics.DeltaMovement}");
|
||||
Assert.InRange(physics.DeltaMovement.X, 0.12, 0.30);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused tests and verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.PathTimingContractTests.RepeatedCardinalParkourChain_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.RepeatedDiagonalParkourChain_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.SprintJumpTemplateScenarioTests.SprintJumpTemplate_LandingRecovery_LeavesEnoughSpeedForNextParkour" -v minimal
|
||||
```
|
||||
|
||||
Expected: FAIL with either `navigation did not complete`, nonzero replans, or residual speed below the handoff minimum.
|
||||
|
||||
- [ ] **Step 3: Make `SprintJumpTemplate` preserve jump-ready handoff instead of over-settling**
|
||||
|
||||
Update `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`:
|
||||
|
||||
```csharp
|
||||
case Phase.Airborne:
|
||||
{
|
||||
if (!physics.OnGround)
|
||||
_leftGround = true;
|
||||
|
||||
bool releaseInAir = ShouldReleaseInAir(pos, physics, world);
|
||||
bool hardRelease = releaseInAir;
|
||||
if (_segment.ExitTransition != PathTransitionType.LandingRecovery && IsPastTarget(pos))
|
||||
hardRelease = true;
|
||||
|
||||
if (hardRelease)
|
||||
{
|
||||
input.Forward = false;
|
||||
input.Sprint = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
}
|
||||
|
||||
if (_leftGround && physics.OnGround)
|
||||
{
|
||||
_phase = Phase.Landing;
|
||||
goto case Phase.Landing;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Phase.Landing:
|
||||
if (ShouldCompleteLandingRecoveryHandoff(pos, physics))
|
||||
return TemplateState.Complete;
|
||||
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
else if (TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment))
|
||||
TemplateHelper.FaceExitHeading(physics, _segment);
|
||||
|
||||
if (_segment.ExitTransition == PathTransitionType.ContinueStraight
|
||||
&& horizDistSq < 2.25
|
||||
&& Math.Abs(dy) < 1.0)
|
||||
{
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
break;
|
||||
|
||||
private bool ShouldCompleteLandingRecoveryHandoff(Location pos, PlayerPhysics physics)
|
||||
{
|
||||
if (_segment.ExitTransition != PathTransitionType.LandingRecovery || _nextSegment is null || !physics.OnGround)
|
||||
return false;
|
||||
|
||||
double exitSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHint(physics, _segment);
|
||||
if (_nextSegment.ExitHints.RequireJumpReady)
|
||||
{
|
||||
return TemplateFootingHelper.IsCenterInsideTargetBlock(pos, ExpectedEnd)
|
||||
&& !TemplateFootingHelper.WillCenterLeaveTargetBlockNextTick(pos, physics, ExpectedEnd)
|
||||
&& exitSpeed >= _nextSegment.ExitHints.MinExitSpeed;
|
||||
}
|
||||
|
||||
return TemplateFootingHelper.IsCenterInsideSupportStrip(pos, ExpectedEnd, _nextSegment.End)
|
||||
&& !TemplateFootingHelper.WillCenterLeaveSupportStripNextTick(pos, physics, ExpectedEnd, _nextSegment.End)
|
||||
&& exitSpeed <= _segment.ExitHints.MaxExitSpeed;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Re-run the parkour-focused tests and then the whole jump-combo contract group**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.PathTimingContractTests.RepeatedCardinalParkourChain_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.RepeatedDiagonalParkourChain_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.JumpCombo_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.SprintJumpTemplateScenarioTests" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS for the two named regressions and no new failures in the broader jump-template coverage.
|
||||
|
||||
- [ ] **Step 5: Commit the parkour landing recovery fix**
|
||||
|
||||
```bash
|
||||
git add MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs \
|
||||
MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs
|
||||
git commit -m "fix: preserve jump-ready speed through parkour landing recovery"
|
||||
```
|
||||
|
||||
### Task 2: Make Braking And Lookahead Respect The Next Segment Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs`
|
||||
|
||||
- [ ] **Step 1: Add failing mixed-route regression tests**
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void MixedTraverseAscendParkourDescend_ExecutionStaysWithinBudget() =>
|
||||
AssertScenarioWithinBudget("mixed-traverse-ascend-parkour-descend");
|
||||
|
||||
[Fact]
|
||||
public void MixedTraverseTurnParkourTurnTraverse_ExecutionStaysWithinBudget() =>
|
||||
AssertScenarioWithinBudget("mixed-traverse-turn-parkour-turn-traverse");
|
||||
|
||||
[Fact]
|
||||
public void SpeedCarryRepeatedTraverseDescend_ExecutionStaysWithinBudget() =>
|
||||
AssertScenarioWithinBudget("speed-carry-repeated-traverse-descend");
|
||||
```
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void ChooseGroundProfile_PicksBrake_WhenLandingRecoveryTurnWouldOvershootSupportStrip()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 108, max: 126);
|
||||
FlatWorldTestBuilder.ClearBox(world, 118, 79, 108, 126, 90, 112);
|
||||
FlatWorldTestBuilder.SetSolid(world, 120, 79, 110);
|
||||
FlatWorldTestBuilder.SetSolid(world, 122, 79, 110);
|
||||
FlatWorldTestBuilder.SetSolid(world, 122, 79, 111);
|
||||
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(120.5, 80, 110.5),
|
||||
End = new Location(122.5, 80, 110.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.LandingRecovery,
|
||||
ExitHints = new PathTransitionHints(0, 1, 0.0, 0.035, true, true, false, true, 12)
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = current.End,
|
||||
End = new Location(122.5, 80, 111.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.PrepareJump,
|
||||
ExitHints = new PathTransitionHints(0, 1, 0.12, double.PositiveInfinity, false, true, true, false, 10),
|
||||
PreserveSprint = true
|
||||
};
|
||||
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(122.58, 80.0, 110.68),
|
||||
DeltaMovement = new Vec3d(0.118, 0.0, 0.018),
|
||||
OnGround = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = 270f
|
||||
};
|
||||
|
||||
TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile(
|
||||
current,
|
||||
next,
|
||||
new Location(122.58, 80.0, 110.68),
|
||||
physics,
|
||||
world);
|
||||
|
||||
Assert.Equal(TransitionInputProfile.Brake, profile);
|
||||
}
|
||||
```
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void Plan_Carries_ForLandingRecovery_WhenNextDescendStillNeedsRunway()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 438, max: 448);
|
||||
FlatWorldTestBuilder.ClearBox(world, 438, 79, 438, 448, 84, 442);
|
||||
FlatWorldTestBuilder.SetSolid(world, 440, 79, 440);
|
||||
FlatWorldTestBuilder.SetSolid(world, 441, 79, 440);
|
||||
FlatWorldTestBuilder.SetSolid(world, 442, 79, 440);
|
||||
FlatWorldTestBuilder.SetSolid(world, 443, 80, 440);
|
||||
FlatWorldTestBuilder.SetSolid(world, 444, 79, 440);
|
||||
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(441.5, 81, 440.5),
|
||||
End = new Location(443.5, 81, 440.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.LandingRecovery,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.0, 0.035, true, true, false, true, 12)
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = current.End,
|
||||
End = new Location(444.5, 80, 440.5),
|
||||
MoveType = MoveType.Descend,
|
||||
ExitTransition = PathTransitionType.FinalStop,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.0, 0.02, true, true, false, false, 12)
|
||||
};
|
||||
|
||||
var physics = CreatePhysics(0.086, 0.0, onGround: true);
|
||||
physics.Position = new Vec3d(443.18, 81.0, 440.5);
|
||||
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(
|
||||
current,
|
||||
next,
|
||||
new Location(443.18, 81.0, 440.5),
|
||||
physics,
|
||||
world);
|
||||
|
||||
Assert.True(decision.HoldForward);
|
||||
Assert.False(decision.HoldBack);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the mixed-route tests and verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.PathTimingContractTests.MixedTraverseAscendParkourDescend_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.MixedTraverseTurnParkourTurnTraverse_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.SpeedCarryRepeatedTraverseDescend_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.TransitionLookaheadEvaluatorTests.ChooseGroundProfile_PicksBrake_WhenLandingRecoveryTurnWouldOvershootSupportStrip|FullyQualifiedName~Pathing.Execution.TransitionBrakingPlannerTests.Plan_Carries_ForLandingRecovery_WhenNextDescendStillNeedsRunway" -v minimal
|
||||
```
|
||||
|
||||
Expected: FAIL because current lookahead and planner logic either brake when the next segment needs carry, or carry when the turn entry should already be slowing down.
|
||||
|
||||
- [ ] **Step 3: Thread `nextSegment` through lookahead scoring and braking decisions**
|
||||
|
||||
Update `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs`:
|
||||
|
||||
```csharp
|
||||
public static TransitionInputProfile ChooseGroundProfile(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world)
|
||||
{
|
||||
double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, current);
|
||||
double forwardSpeed = Math.Max(0.0,
|
||||
TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ));
|
||||
|
||||
bool requiresJumpEntry = current.ExitHints.RequireJumpReady
|
||||
|| current.ExitTransition == PathTransitionType.PrepareJump;
|
||||
|
||||
if (current.ExitTransition == PathTransitionType.ContinueStraight && !requiresJumpEntry)
|
||||
return TransitionInputProfile.Carry;
|
||||
|
||||
if (requiresJumpEntry)
|
||||
return TransitionInputProfile.Carry;
|
||||
|
||||
if (next is not null && current.ExitTransition == PathTransitionType.LandingRecovery)
|
||||
{
|
||||
bool headingChange = current.HeadingX != next.HeadingX || current.HeadingZ != next.HeadingZ;
|
||||
if (headingChange && forwardSpeed > GetTargetMaxExitSpeed(current))
|
||||
return TransitionInputProfile.Brake;
|
||||
|
||||
if (next.ExitHints.RequireJumpReady && forwardSpeed < next.ExitHints.MinExitSpeed)
|
||||
return TransitionInputProfile.Carry;
|
||||
}
|
||||
|
||||
bool requiresSlowEntry = current.ExitHints.RequireStableFooting
|
||||
|| current.ExitTransition is PathTransitionType.FinalStop or PathTransitionType.Turn
|
||||
|| (current.ExitTransition == PathTransitionType.LandingRecovery
|
||||
&& (current.ExitHints.AllowAirBrake || IsFiniteSpeedCap(current)));
|
||||
|
||||
if (!requiresSlowEntry)
|
||||
return TransitionInputProfile.Carry;
|
||||
|
||||
double maxExitSpeed = GetTargetMaxExitSpeed(current);
|
||||
double hardBrakeDistance = TransitionBrakingPlanner.EstimateGroundStopDistance(
|
||||
physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: true);
|
||||
double coastStopDistance = TransitionBrakingPlanner.EstimateGroundStopDistance(
|
||||
physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: false);
|
||||
|
||||
if (remaining < 0.0)
|
||||
return TransitionInputProfile.Brake;
|
||||
|
||||
if (forwardSpeed > maxExitSpeed && remaining <= hardBrakeDistance + 0.10)
|
||||
return TransitionInputProfile.Brake;
|
||||
|
||||
if (forwardSpeed <= maxExitSpeed && remaining > 0.0)
|
||||
return TransitionInputProfile.Carry;
|
||||
|
||||
if (remaining <= coastStopDistance + 0.06)
|
||||
return TransitionInputProfile.Coast;
|
||||
|
||||
return TransitionInputProfile.Carry;
|
||||
}
|
||||
|
||||
public static TransitionInputProfile ChooseAirProfile(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world)
|
||||
{
|
||||
if (!current.ExitHints.AllowAirBrake)
|
||||
return TransitionInputProfile.AirHoldForward;
|
||||
|
||||
TransitionInputProfile[] candidates =
|
||||
[
|
||||
TransitionInputProfile.AirHoldForward,
|
||||
TransitionInputProfile.AirRelease,
|
||||
TransitionInputProfile.AirBrake
|
||||
];
|
||||
|
||||
return ChooseBest(current, next, pos, physics, world, candidates);
|
||||
}
|
||||
|
||||
private static TransitionInputProfile ChooseBest(PathSegment segment, PathSegment? next, Location pos, PlayerPhysics physics, World world,
|
||||
TransitionInputProfile[] candidates)
|
||||
{
|
||||
TransitionInputProfile best = candidates[0];
|
||||
double bestScore = double.PositiveInfinity;
|
||||
|
||||
foreach (TransitionInputProfile candidate in candidates)
|
||||
{
|
||||
double score = Score(segment, next, pos, physics, world, candidate);
|
||||
if (score < bestScore)
|
||||
{
|
||||
best = candidate;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
private static double Score(PathSegment segment, PathSegment? next, Location pos, PlayerPhysics physics, World world, TransitionInputProfile candidate)
|
||||
{
|
||||
PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics);
|
||||
sim.Position = new Vec3d(pos.X, pos.Y, pos.Z);
|
||||
|
||||
var input = new MovementInput();
|
||||
Location simPos = pos;
|
||||
|
||||
for (int tick = 0; tick < segment.ExitHints.HorizonTicks; tick++)
|
||||
{
|
||||
if (TemplateHelper.ShouldBiasTowardExitHeading(simPos, segment))
|
||||
TemplateHelper.FaceExitHeading(sim, segment);
|
||||
|
||||
input.Reset();
|
||||
ApplyCandidateInput(input, candidate, segment);
|
||||
sim.ApplyInput(input);
|
||||
sim.Tick(world);
|
||||
simPos = new Location(sim.Position.X, sim.Position.Y, sim.Position.Z);
|
||||
}
|
||||
|
||||
double score = ScoreNextSegmentEntry(segment, next, simPos, sim);
|
||||
score += TemplateHelper.HeadingPenaltyDegrees(sim.Yaw, segment);
|
||||
score += Math.Abs(TemplateHelper.RemainingDistanceAlongSegment(simPos, segment)) * 10.0;
|
||||
return score;
|
||||
}
|
||||
|
||||
private static double ScoreNextSegmentEntry(PathSegment current, PathSegment? next, Location simPos, PlayerPhysics sim)
|
||||
{
|
||||
if (next is null)
|
||||
return 0.0;
|
||||
|
||||
double score = 0.0;
|
||||
|
||||
if (current.ExitTransition == PathTransitionType.LandingRecovery
|
||||
&& (current.HeadingX != next.HeadingX || current.HeadingZ != next.HeadingZ)
|
||||
&& !TemplateFootingHelper.IsCenterInsideSupportStrip(simPos, current.End, next.End))
|
||||
{
|
||||
score += 1200.0;
|
||||
}
|
||||
|
||||
if (next.ExitHints.RequireJumpReady)
|
||||
{
|
||||
double nextSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHeading(sim, next.HeadingX, next.HeadingZ);
|
||||
if (nextSpeed < next.ExitHints.MinExitSpeed)
|
||||
score += (next.ExitHints.MinExitSpeed - nextSpeed) * 600.0;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
```
|
||||
|
||||
Update `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs`:
|
||||
|
||||
```csharp
|
||||
TransitionInputProfile profile;
|
||||
if (physics.OnGround)
|
||||
{
|
||||
profile = TransitionLookaheadEvaluator.ChooseGroundProfile(current, next, pos, physics, world);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!current.ExitHints.AllowAirBrake)
|
||||
return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint);
|
||||
|
||||
profile = TransitionLookaheadEvaluator.ChooseAirProfile(current, next, pos, physics, world);
|
||||
}
|
||||
|
||||
return profile switch
|
||||
{
|
||||
TransitionInputProfile.Carry => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint || next?.ExitHints.RequireJumpReady == true),
|
||||
TransitionInputProfile.Coast => TransitionBrakingDecision.Coast,
|
||||
TransitionInputProfile.Brake => TransitionBrakingDecision.Brake,
|
||||
TransitionInputProfile.AirHoldForward => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint || next?.ExitHints.RequireJumpReady == true),
|
||||
TransitionInputProfile.AirRelease => TransitionBrakingDecision.Coast,
|
||||
TransitionInputProfile.AirBrake => TransitionBrakingDecision.Brake,
|
||||
_ => TransitionBrakingDecision.Coast
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Re-run focused mixed-route tests and the broader long-route contract group**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.PathTimingContractTests.MixedTraverseAscendParkourDescend_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.MixedTraverseTurnParkourTurnTraverse_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.SpeedCarryRepeatedTraverseDescend_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.LongRoute_ExecutionStaysWithinBudget|FullyQualifiedName~Pathing.Execution.TransitionLookaheadEvaluatorTests|FullyQualifiedName~Pathing.Execution.TransitionBrakingPlannerTests" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS for the new explicit regressions and no new failures in the broader lookahead/braking coverage.
|
||||
|
||||
- [ ] **Step 5: Commit the mixed-route braking fix**
|
||||
|
||||
```bash
|
||||
git add MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs \
|
||||
MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs \
|
||||
MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs
|
||||
git commit -m "fix: align transition lookahead with next segment entry"
|
||||
```
|
||||
|
||||
### Task 3: Remove Chained-Ascend Landing Stall In Live Staircases
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
|
||||
- [ ] **Step 1: Add a failing chained-ascend convergence test**
|
||||
|
||||
Update `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void AscendTemplate_ContinueStraight_CompletesWithoutSettlingToZeroSpeed()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 338, max: 347);
|
||||
FlatWorldTestBuilder.ClearBox(world, 340, 80, 338, 347, 86, 342);
|
||||
FlatWorldTestBuilder.FillSolid(world, 341, 80, 339, 341, 80, 341);
|
||||
FlatWorldTestBuilder.FillSolid(world, 342, 81, 339, 342, 81, 341);
|
||||
FlatWorldTestBuilder.FillSolid(world, 343, 82, 339, 343, 82, 341);
|
||||
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(341.5, 81, 340.5),
|
||||
End = new Location(342.5, 82, 340.5),
|
||||
MoveType = MoveType.Ascend,
|
||||
ExitTransition = PathTransitionType.ContinueStraight,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.08, double.PositiveInfinity, false, true, false, false, 8),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = current.End,
|
||||
End = new Location(343.5, 83, 340.5),
|
||||
MoveType = MoveType.Ascend,
|
||||
ExitTransition = PathTransitionType.ContinueStraight,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.08, double.PositiveInfinity, false, true, false, false, 8),
|
||||
PreserveSprint = true
|
||||
};
|
||||
|
||||
var template = new AscendTemplate(current, next);
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(current.Start.X, current.Start.Y, current.Start.Z),
|
||||
DeltaMovement = new Vec3d(0.11, 0.0, 0.0),
|
||||
OnGround = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = 270f,
|
||||
Pitch = 0f
|
||||
};
|
||||
|
||||
var input = new MovementInput();
|
||||
TemplateState state = TemplateState.InProgress;
|
||||
int ticks = 0;
|
||||
for (; ticks < 30; ticks++)
|
||||
{
|
||||
input.Reset();
|
||||
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
state = template.Tick(pos, physics, input, world);
|
||||
if (state != TemplateState.InProgress)
|
||||
break;
|
||||
|
||||
physics.ApplyInput(input);
|
||||
physics.Tick(world);
|
||||
}
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
Assert.InRange(ticks, 1, 14);
|
||||
Assert.InRange(physics.DeltaMovement.X, 0.05, 0.20);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the new unit test and the live long-route harness to confirm current failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.GroundedTemplateConvergenceTests.AscendTemplate_ContinueStraight_CompletesWithoutSettlingToZeroSpeed -v minimal
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected: the unit test fails on tick count or residual speed, and the live harness still reports `same-move-ascend-staircase` over budget.
|
||||
|
||||
- [ ] **Step 3: Split ascend execution into takeoff, airborne, and landing handoff**
|
||||
|
||||
Update `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`:
|
||||
|
||||
```csharp
|
||||
private enum Phase { Takeoff, Airborne, Landing }
|
||||
|
||||
private Phase _phase = Phase.Takeoff;
|
||||
private bool _leftGround;
|
||||
private int _landingTicks;
|
||||
|
||||
public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world)
|
||||
{
|
||||
_tickCount++;
|
||||
|
||||
double dx = ExpectedEnd.X - pos.X;
|
||||
double dz = ExpectedEnd.Z - pos.Z;
|
||||
double dy = ExpectedEnd.Y - pos.Y;
|
||||
|
||||
float targetYaw = TemplateHelper.CalculateYaw(dx, dz);
|
||||
float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz);
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
|
||||
switch (_phase)
|
||||
{
|
||||
case Phase.Takeoff:
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
if (physics.OnGround && dy > 0.1)
|
||||
{
|
||||
input.Jump = true;
|
||||
_phase = Phase.Airborne;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase.Airborne:
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
if (!physics.OnGround)
|
||||
_leftGround = true;
|
||||
if (_leftGround && physics.OnGround)
|
||||
{
|
||||
_phase = Phase.Landing;
|
||||
_landingTicks = 0;
|
||||
goto case Phase.Landing;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase.Landing:
|
||||
_landingTicks++;
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_stuckTicks > 20 || _tickCount > 50)
|
||||
return TemplateState.Failed;
|
||||
|
||||
return TemplateState.InProgress;
|
||||
}
|
||||
```
|
||||
|
||||
Update `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`:
|
||||
|
||||
```csharp
|
||||
if (segment.MoveType == MoveType.Ascend
|
||||
&& segment.ExitTransition == PathTransitionType.ContinueStraight
|
||||
&& physics.OnGround
|
||||
&& TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, segment.End)
|
||||
&& !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, segment.End))
|
||||
{
|
||||
double exitSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHint(physics, segment);
|
||||
return exitSpeed >= Math.Max(0.02, segment.ExitHints.MinExitSpeed);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Re-run the ascend convergence test and the live long-route harness**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.GroundedTemplateConvergenceTests.AscendTemplate_ContinueStraight_CompletesWithoutSettlingToZeroSpeed|FullyQualifiedName~Pathing.Execution.PathTimingContractTests.Scenario_ExecutionStaysWithinTimingBudget" -v minimal
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected: PASS for the new unit test and the live long-route suite, including `same-move-ascend-staircase`.
|
||||
|
||||
- [ ] **Step 5: Commit the ascend convergence fix**
|
||||
|
||||
```bash
|
||||
git add MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs \
|
||||
MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs \
|
||||
MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs
|
||||
git commit -m "fix: reduce chained ascend landing stalls"
|
||||
```
|
||||
|
||||
### Task 4: Run The Full Regression Sweep And Stop On Any Residual Family
|
||||
|
||||
**Files:**
|
||||
- No code changes required unless verification reveals a new, scoped defect
|
||||
|
||||
- [ ] **Step 1: Re-run all focused pathing execution tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS with `0` failing pathing execution tests.
|
||||
|
||||
- [ ] **Step 2: Re-run the live accepted-route suites that previously failed**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-jump-combos.sh 1.21.11-Vanilla
|
||||
source tools/mcc-env.sh && bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected: both scripts exit `0`, with no accepted-route replans and no contract-budget overruns.
|
||||
|
||||
- [ ] **Step 3: If any live case still fails, capture the exact family before doing more coding**
|
||||
|
||||
Use the existing contract report output already printed by the harnesses. Record:
|
||||
|
||||
```text
|
||||
scenario id
|
||||
total actual / max ticks
|
||||
which segment index exceeded
|
||||
whether the failure was replan, timeout, or budget overrun
|
||||
```
|
||||
|
||||
Do not widen scope beyond:
|
||||
|
||||
- parkour landing recovery
|
||||
- next-segment braking/lookahead
|
||||
- chained ascend landing handoff
|
||||
|
||||
- [ ] **Step 4: End the plan cleanly once verification is green**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: only the intentional runtime/test edits from Tasks 1 through 3 remain. If verification is green and no extra follow-up patch was needed, do not create an empty commit. If verification exposes a new defect family, stop and write a separate scoped plan instead of slipping extra repair work into this one.
|
||||
|
||||
## Self-Review
|
||||
|
||||
Spec coverage check:
|
||||
|
||||
- repeated parkour failures map to Task 1
|
||||
- mixed-route carry/brake failures map to Task 2
|
||||
- live staircase ascend overrun maps to Task 3
|
||||
- full xUnit and live verification maps to Task 4
|
||||
|
||||
Placeholder scan:
|
||||
|
||||
- no `TODO`, `TBD`, or “similar to above” placeholders remain
|
||||
- each task includes concrete file paths, test code, commands, and commit steps
|
||||
|
||||
Type consistency:
|
||||
|
||||
- all next-segment-aware changes consistently use `PathSegment? next`
|
||||
- named test helpers use `AssertScenarioWithinBudget`
|
||||
- runtime fixes stay inside the already failing execution files
|
||||
520
docs/superpowers/plans/2026-04-15-jump-entry-direct-yaw.md
Normal file
520
docs/superpowers/plans/2026-04-15-jump-entry-direct-yaw.md
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
# Jump-Entry Direct Yaw Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Remove unnecessary yaw smoothing in jump-entry states so opposite-yaw jump starts commit immediately without changing normal walk, descend, climb, or final-stop behavior.
|
||||
|
||||
**Architecture:** Introduce a small helper-level yaw alignment policy, then opt in only the jump-entry states: sprint-jump approach, ascend pre-jump alignment, grounded prepare-jump freeze, and grounded walk segments that are explicitly preparing a jump. Keep air control, grounded braking, descend, climb, and ordinary walk/final-stop behavior on smooth yaw, and prove the scope boundary with focused unit tests plus sequential live harness runs.
|
||||
|
||||
**Tech Stack:** C# 14, .NET 10, xUnit, MCC local harness scripts (`tools/mcc-env.sh`, `mcc-preflight`, `tools/test-pathing-jump-combos.sh`, `tools/test-pathing-long-routes.sh`)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`
|
||||
- Add a small yaw-alignment helper and heading-facing overloads so templates can request `Smooth` or `Snap` without open-coding raw yaw assignment.
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`
|
||||
- Snap yaw only during `Phase.Approach`; keep air and landing phases on smooth yaw.
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- Snap yaw only while aligning for jump commitment; preserve the existing grounded prepare-jump handoff carveout.
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- Snap exit heading in the frozen `PrepareJump` turn branch only.
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs`
|
||||
- Use snap yaw only for grounded `PrepareJump` segments with `ExitHints.RequireJumpReady == true`.
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- Add a focused regression that proves sprint-jump approach snaps immediately from opposite yaw.
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Add focused regressions for ascend pre-jump snap, walk run-up snap, grounded freeze snap, and ordinary final-stop smoothness.
|
||||
|
||||
### Task 1: Add Failing Sprint-Jump Snap Test
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- Test: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add this test near the existing opposite-yaw sprint-jump regressions:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void SprintJumpTemplate_Approach_SnapsYawImmediatelyFromOppositeYaw()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16);
|
||||
FlatWorldTestBuilder.ClearBox(world, 0, 79, 0, 4, 82, 1);
|
||||
FlatWorldTestBuilder.SetSolid(world, 0, 79, 0);
|
||||
FlatWorldTestBuilder.SetSolid(world, 2, 79, 0);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(2.5, 80, 0.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new SprintJumpTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 90f);
|
||||
var input = new MovementInput();
|
||||
|
||||
TemplateState state = template.Tick(segment.Start, physics, input, world);
|
||||
|
||||
Assert.Equal(TemplateState.InProgress, state);
|
||||
Assert.InRange(physics.Yaw, 269.9f, 270.1f);
|
||||
Assert.True(input.Forward);
|
||||
Assert.True(input.Sprint);
|
||||
Assert.True(input.Jump);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplate_Approach_SnapsYawImmediatelyFromOppositeYaw" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `FAIL`
|
||||
- The failure should show `physics.Yaw` still near `125` and movement input still blocked by the turn-in-place gate.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
In `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`, add the alignment helper and overloads:
|
||||
|
||||
```csharp
|
||||
internal enum YawAlignmentMode
|
||||
{
|
||||
Smooth,
|
||||
Snap
|
||||
}
|
||||
|
||||
internal static float AlignYaw(float current, float target, YawAlignmentMode mode, float maxStep = MaxYawStepPerTick)
|
||||
{
|
||||
target = NormalizeYaw(target);
|
||||
return mode == YawAlignmentMode.Snap
|
||||
? target
|
||||
: SmoothYaw(current, target, maxStep);
|
||||
}
|
||||
|
||||
internal static void FaceSegmentHeading(PlayerPhysics physics, PathSegment segment, YawAlignmentMode mode = YawAlignmentMode.Smooth)
|
||||
{
|
||||
float headingYaw = CalculateYaw(segment.HeadingX, segment.HeadingZ);
|
||||
physics.Yaw = AlignYaw(physics.Yaw, headingYaw, mode);
|
||||
}
|
||||
|
||||
internal static void FaceExitHeading(PlayerPhysics physics, PathSegment segment, YawAlignmentMode mode = YawAlignmentMode.Smooth)
|
||||
{
|
||||
float headingYaw = GetExitHeadingYaw(segment);
|
||||
physics.Yaw = AlignYaw(physics.Yaw, headingYaw, mode);
|
||||
}
|
||||
|
||||
private static float NormalizeYaw(float yaw)
|
||||
{
|
||||
while (yaw < 0f) yaw += 360f;
|
||||
while (yaw >= 360f) yaw -= 360f;
|
||||
return yaw;
|
||||
}
|
||||
```
|
||||
|
||||
In `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`, switch only `Phase.Approach` to snap yaw:
|
||||
|
||||
```csharp
|
||||
YawAlignmentMode yawMode = _phase == Phase.Approach
|
||||
? YawAlignmentMode.Snap
|
||||
: YawAlignmentMode.Smooth;
|
||||
|
||||
physics.Yaw = TemplateHelper.AlignYaw(physics.Yaw, targetYaw, yawMode);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplate_Approach_SnapsYawImmediatelyFromOppositeYaw|FullyQualifiedName~SprintJumpTemplate_TwoBlockGap_FinalStop_CompletesFromOppositeYawWithinTwentyTicks|FullyQualifiedName~SprintJumpTemplate_ThreeBlockGap_FinalStop_Completes" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `PASS`
|
||||
- The new test passes.
|
||||
- The existing opposite-yaw timing regression stays green.
|
||||
- The 3-block final-stop sprint jump still completes.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \
|
||||
MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs
|
||||
git commit -m "pathing: snap yaw for sprint jump approach"
|
||||
```
|
||||
|
||||
### Task 2: Add Failing Ascend And Frozen Prepare-Jump Snap Tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- Test: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
Add these tests to `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` near the existing prepare-jump regressions:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void AscendTemplate_PrepareJump_SnapsYawImmediatelyFromOppositeYaw()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 338, max: 344);
|
||||
FlatWorldTestBuilder.ClearBox(world, 340, 80, 338, 344, 84, 342);
|
||||
FlatWorldTestBuilder.FillSolid(world, 341, 80, 339, 341, 80, 341);
|
||||
FlatWorldTestBuilder.FillSolid(world, 342, 81, 339, 342, 81, 341);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(340.5, 80, 340.5),
|
||||
End = new Location(341.5, 81, 340.5),
|
||||
MoveType = MoveType.Ascend,
|
||||
ExitTransition = PathTransitionType.PrepareJump,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.10, double.PositiveInfinity, false, true, true, false, 10),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = new Location(341.5, 81, 340.5),
|
||||
End = new Location(342.5, 82, 340.5),
|
||||
MoveType = MoveType.Ascend,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new AscendTemplate(segment, next);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 90f);
|
||||
var input = new MovementInput();
|
||||
|
||||
TemplateState state = template.Tick(segment.Start, physics, input, world);
|
||||
|
||||
Assert.Equal(TemplateState.InProgress, state);
|
||||
Assert.InRange(physics.Yaw, 269.9f, 270.1f);
|
||||
Assert.True(input.Forward);
|
||||
Assert.True(input.Sprint);
|
||||
Assert.True(input.Jump);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WalkTemplate_PrepareJump_FreezeForTurn_SnapsExitHeadingImmediately()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 0.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.PrepareJump,
|
||||
ExitHints = new PathTransitionHints(0, 1, 0.10, double.PositiveInfinity, false, true, true, false, 10),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = new Location(1.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 1.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(current, next);
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(1.5, 80.0, 0.5),
|
||||
DeltaMovement = Vec3d.Zero,
|
||||
OnGround = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = 180f,
|
||||
Pitch = 0f
|
||||
};
|
||||
var input = new MovementInput();
|
||||
|
||||
TemplateState state = template.Tick(new Location(1.5, 80, 0.5), physics, input, world);
|
||||
|
||||
Assert.Equal(TemplateState.InProgress, state);
|
||||
Assert.InRange(physics.Yaw, -0.1f, 0.1f);
|
||||
Assert.False(input.Forward);
|
||||
Assert.False(input.Sprint);
|
||||
Assert.False(input.Back);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~AscendTemplate_PrepareJump_SnapsYawImmediatelyFromOppositeYaw|FullyQualifiedName~WalkTemplate_PrepareJump_FreezeForTurn_SnapsExitHeadingImmediately" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `FAIL`
|
||||
- The ascend test should show yaw still part-way through the turn.
|
||||
- The frozen prepare-jump test should show yaw still around `145` instead of `0`.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
In `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`, snap yaw only before jump commitment and keep the handoff carveout:
|
||||
|
||||
```csharp
|
||||
bool snapYawForJumpCommit = !_initiatedJump && !groundedPrepareJumpHandoff;
|
||||
physics.Yaw = TemplateHelper.AlignYaw(
|
||||
physics.Yaw,
|
||||
targetYaw,
|
||||
snapYawForJumpCommit ? YawAlignmentMode.Snap : YawAlignmentMode.Smooth);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
```
|
||||
|
||||
In `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`, snap the frozen exit-heading turn:
|
||||
|
||||
```csharp
|
||||
if (segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& segment.ExitHints.RequireJumpReady
|
||||
&& physics.OnGround
|
||||
&& TemplateFootingHelper.IsCenterInsideTargetBlock(pos, segment.End)
|
||||
&& IsReadyToFreezeForTurn(segment, pos)
|
||||
&& TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment) > 8.0)
|
||||
{
|
||||
input.Forward = false;
|
||||
input.Sprint = false;
|
||||
input.Back = false;
|
||||
TemplateHelper.FaceExitHeading(physics, segment, YawAlignmentMode.Snap);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~AscendTemplate_PrepareJump_SnapsYawImmediatelyFromOppositeYaw|FullyQualifiedName~WalkTemplate_PrepareJump_FreezeForTurn_SnapsExitHeadingImmediately|FullyQualifiedName~AscendTemplate_PrepareJump_CompletesFromOppositeYawWithinTwentyTicks|FullyQualifiedName~WalkTemplate_TurnIntoParkour_CompletesOnlyWhenTurnEntryIsSlowAndJumpReady" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `PASS`
|
||||
- The new snap regressions pass.
|
||||
- Existing opposite-yaw ascend timing stays green.
|
||||
- The turn-into-parkour convergence regression still passes.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs \
|
||||
MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs
|
||||
git commit -m "pathing: snap yaw for jump-ready grounded handoffs"
|
||||
```
|
||||
|
||||
### Task 3: Add Failing Walk Jump-Entry Scope Tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs`
|
||||
- Test: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
Add these tests to `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` near the existing walk prepare-jump coverage:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void WalkTemplate_PrepareJump_SnapsYawImmediatelyDuringRunUp()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 0.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.PrepareJump,
|
||||
ExitHints = new PathTransitionHints(1, 0, 0.10, double.PositiveInfinity, false, true, true, false, 10),
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = new Location(1.5, 80, 0.5),
|
||||
End = new Location(3.5, 80, 0.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(current, next);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 90f);
|
||||
var input = new MovementInput();
|
||||
|
||||
TemplateState state = template.Tick(current.Start, physics, input, world);
|
||||
|
||||
Assert.Equal(TemplateState.InProgress, state);
|
||||
Assert.InRange(physics.Yaw, 269.9f, 270.1f);
|
||||
Assert.True(input.Forward);
|
||||
Assert.True(input.Sprint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WalkTemplate_FinalStop_RetainsSmoothYawOutsideJumpEntry()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 0.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 90f);
|
||||
var input = new MovementInput();
|
||||
|
||||
TemplateState state = template.Tick(segment.Start, physics, input, world);
|
||||
|
||||
Assert.Equal(TemplateState.InProgress, state);
|
||||
Assert.InRange(physics.Yaw, 124.9f, 125.1f);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~WalkTemplate_PrepareJump_SnapsYawImmediatelyDuringRunUp|FullyQualifiedName~WalkTemplate_FinalStop_RetainsSmoothYawOutsideJumpEntry" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `FAIL`
|
||||
- The prepare-jump test should show smooth partial rotation instead of an immediate snap.
|
||||
- The final-stop control test should already pass and act as the scope guard for the next step.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
In `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs`, gate snap yaw to grounded jump-entry segments only:
|
||||
|
||||
```csharp
|
||||
bool snapYawForJumpEntry = physics.OnGround
|
||||
&& _segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& _segment.ExitHints.RequireJumpReady;
|
||||
|
||||
float targetYaw = TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment)
|
||||
? TemplateHelper.GetExitHeadingYaw(_segment)
|
||||
: TemplateHelper.CalculateYaw(dx, dz);
|
||||
|
||||
physics.Yaw = TemplateHelper.AlignYaw(
|
||||
physics.Yaw,
|
||||
targetYaw,
|
||||
snapYawForJumpEntry ? YawAlignmentMode.Snap : YawAlignmentMode.Smooth);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~WalkTemplate_PrepareJump_SnapsYawImmediatelyDuringRunUp|FullyQualifiedName~WalkTemplate_FinalStop_RetainsSmoothYawOutsideJumpEntry|FullyQualifiedName~WalkTemplate_PrepareJump_CompletesWithoutSettlingOnRunUpBlock|FullyQualifiedName~WalkTemplate_DiagonalPrepareJumpIntoAscend_CompletesFromTargetBlockEntry" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `PASS`
|
||||
- The new run-up snap regression passes.
|
||||
- The final-stop scope guard stays green.
|
||||
- Existing walk prepare-jump convergence regressions remain green.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs \
|
||||
MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs
|
||||
git commit -m "pathing: snap yaw only for grounded jump-entry walk states"
|
||||
```
|
||||
|
||||
### Task 4: Full Verification And Evidence Capture
|
||||
|
||||
**Files:**
|
||||
- Modify only if timing evidence demands it:
|
||||
- `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json`
|
||||
- `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json`
|
||||
- Verify:
|
||||
- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs`
|
||||
- `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs`
|
||||
- `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs`
|
||||
|
||||
- [ ] **Step 1: Run the focused unit regression set**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplateScenarioTests|FullyQualifiedName~GroundedTemplateConvergenceTests|FullyQualifiedName~LivePathingRegressionTests|FullyQualifiedName~MoveParkourTests.Accepts4x1JumpWithoutRearSupport_WhenTakeoffBlockProvidesRunway|FullyQualifiedName~PathPlanningContractTests.Scenario_PlannerMatchesContract|FullyQualifiedName~PathTimingContractTests.JumpCombo_ExecutionStaysWithinBudget|FullyQualifiedName~PathTimingContractTests.LongRoute_ExecutionStaysWithinBudget" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `PASS`
|
||||
- No planner regressions.
|
||||
- No timing budget failures.
|
||||
|
||||
- [ ] **Step 2: If a timing contract fails, refresh it from evidence before rerunning**
|
||||
|
||||
Use the bootstrap printer first:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathingContractBootstrapTests" -v minimal
|
||||
```
|
||||
|
||||
Only if a contract mismatch is stable and explained by the new snap behavior, update the matching JSON entries with the printed values, then rerun the focused contract tests:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathPlanningContractTests.Scenario_PlannerMatchesContract|FullyQualifiedName~PathTimingContractTests.JumpCombo_ExecutionStaysWithinBudget|FullyQualifiedName~PathTimingContractTests.LongRoute_ExecutionStaysWithinBudget" -v minimal
|
||||
```
|
||||
|
||||
Expected:
|
||||
- Either no JSON changes are needed, or the rerun passes with fresh values backed by bootstrap output.
|
||||
|
||||
- [ ] **Step 3: Run jump-combo live harness sequentially**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
bash -lc 'source tools/mcc-env.sh && mcc-preflight 1.21.11-Vanilla && bash tools/test-pathing-jump-combos.sh 1.21.11-Vanilla'
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `PASS` summary for all jump-combo scenarios.
|
||||
- No `Replan #`, `Partial`, `Replan failed`, or `Giving up`.
|
||||
|
||||
- [ ] **Step 4: Run long-route live harness sequentially**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
bash -lc 'source tools/mcc-env.sh && mcc-preflight 1.21.11-Vanilla && bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla'
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `Pathing long-route suite complete.`
|
||||
- No `Replan #`, `Partial`, `Replan failed`, or `Giving up`.
|
||||
- Repeated jump-entry routes remain within current max budgets.
|
||||
|
||||
- [ ] **Step 5: Commit only additional contract refreshes from Task 4**
|
||||
|
||||
If Task 4 needed no JSON or script edits, do not create another commit. Record that verification completed with no additional file changes.
|
||||
|
||||
If timing contracts changed in Task 4, commit only those refreshes:
|
||||
|
||||
```bash
|
||||
git add MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json
|
||||
git commit -m "test: refresh jump-entry snap yaw timing budgets"
|
||||
```
|
||||
|
|
@ -183,11 +183,13 @@ prepare_independent_route() {
|
|||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local start_yaw="${5:-270}"
|
||||
local start_pitch="${6:-0}"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z $start_yaw $start_pitch" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
|
|
@ -261,9 +263,11 @@ run_accepted_route() {
|
|||
local goal_x="$6"
|
||||
local goal_y="$7"
|
||||
local goal_z="$8"
|
||||
local timeout="${9:-45}"
|
||||
local start_yaw="${9:-270}"
|
||||
local start_pitch="${10:-0}"
|
||||
local timeout="${11:-45}"
|
||||
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z"
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z" "$start_yaw" "$start_pitch"
|
||||
capture_debug_state_before_route "$label"
|
||||
|
||||
local start_line
|
||||
|
|
@ -327,7 +331,7 @@ scenario_repeated_cardinal_parkour() {
|
|||
set_stone 584 79 580
|
||||
set_stone 586 79 580
|
||||
set_stone 588 79 580
|
||||
run_accepted_route "repeated-cardinal-parkour-chain" "Repeated jump - cardinal parkour chain" "580.5" "80" "580.5" "588" "80.00" "580"
|
||||
run_accepted_route "repeated-cardinal-parkour-chain" "Repeated jump - cardinal parkour chain" "580.5" "80" "580.5" "588" "80.00" "580" "270"
|
||||
}
|
||||
|
||||
scenario_repeated_diagonal_parkour() {
|
||||
|
|
@ -337,7 +341,7 @@ scenario_repeated_diagonal_parkour() {
|
|||
set_stone 602 79 602
|
||||
set_stone 604 79 604
|
||||
set_stone 606 79 606
|
||||
run_accepted_route "repeated-diagonal-parkour-chain" "Repeated jump - diagonal parkour chain" "600.5" "80" "600.5" "606" "80.00" "606"
|
||||
run_accepted_route "repeated-diagonal-parkour-chain" "Repeated jump - diagonal parkour chain" "600.5" "80" "600.5" "606" "80.00" "606" "315"
|
||||
}
|
||||
|
||||
scenario_obstructed_parkour_turn_mix() {
|
||||
|
|
@ -353,7 +357,7 @@ scenario_obstructed_parkour_turn_mix() {
|
|||
set_stone 620 81 621
|
||||
set_stone 622 80 622
|
||||
set_stone 622 81 622
|
||||
run_accepted_route "obstructed-parkour-l-turns" "Obstructed jump mix - repeated parkour L-turns" "620.5" "80" "620.5" "626" "80.00" "622"
|
||||
run_accepted_route "obstructed-parkour-l-turns" "Obstructed jump mix - repeated parkour L-turns" "620.5" "80" "620.5" "626" "80.00" "622" "270"
|
||||
}
|
||||
|
||||
scenario_parkour_ascend_descend_chain() {
|
||||
|
|
@ -364,7 +368,7 @@ scenario_parkour_ascend_descend_chain() {
|
|||
set_stone 644 79 620
|
||||
set_stone 646 80 620
|
||||
set_stone 648 79 620
|
||||
run_accepted_route "vertical-jump-mix" "Vertical jump mix - parkour ascend descend chain" "640.5" "80" "620.5" "648" "80.00" "620"
|
||||
run_accepted_route "vertical-jump-mix" "Vertical jump mix - parkour ascend descend chain" "640.5" "80" "620.5" "648" "80.00" "620" "270"
|
||||
}
|
||||
|
||||
scenario_diagonal_ascend_descend_chain() {
|
||||
|
|
@ -375,7 +379,7 @@ scenario_diagonal_ascend_descend_chain() {
|
|||
set_stone 682 79 622
|
||||
set_stone 683 80 623
|
||||
set_stone 684 79 624
|
||||
run_accepted_route "diagonal-vertical-mix" "Diagonal vertical mix - ascend descend chain" "680.5" "80" "620.5" "684" "80.00" "624"
|
||||
run_accepted_route "diagonal-vertical-mix" "Diagonal vertical mix - ascend descend chain" "680.5" "80" "620.5" "684" "80.00" "624" "315"
|
||||
}
|
||||
|
||||
start_mcc
|
||||
|
|
|
|||
|
|
@ -144,11 +144,13 @@ prepare_independent_route() {
|
|||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local start_yaw="${5:-270}"
|
||||
local start_pitch="${6:-0}"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z $start_yaw $start_pitch" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
|
|
@ -258,9 +260,11 @@ run_accepted_route() {
|
|||
local goal_x="$6"
|
||||
local goal_y="$7"
|
||||
local goal_z="$8"
|
||||
local timeout="${9:-45}"
|
||||
local start_yaw="${9:-270}"
|
||||
local start_pitch="${10:-0}"
|
||||
local timeout="${11:-45}"
|
||||
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z"
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z" "$start_yaw" "$start_pitch"
|
||||
capture_debug_state_before_route "$label"
|
||||
|
||||
local start_line
|
||||
|
|
@ -299,7 +303,7 @@ run_same_move_routes() {
|
|||
fill_box 298 79 298 314 79 302 air
|
||||
fill_box 298 80 298 314 90 302 air
|
||||
fill_box 300 79 300 312 79 300 stone
|
||||
run_accepted_route "same-move-straight-traverse-chain" "Same move - straight traverse chain" "300.5" "80" "300.5" "312" "80.00" "300"
|
||||
run_accepted_route "same-move-straight-traverse-chain" "Same move - straight traverse chain" "300.5" "80" "300.5" "312" "80.00" "300" "270"
|
||||
|
||||
fill_box 318 79 318 330 79 330 air
|
||||
fill_box 318 80 318 330 90 330 air
|
||||
|
|
@ -311,7 +315,7 @@ run_same_move_routes() {
|
|||
set_stone 325 79 325
|
||||
set_stone 326 79 326
|
||||
set_stone 327 79 327
|
||||
run_accepted_route "same-move-diagonal-chain" "Same move - diagonal chain" "320.5" "80" "320.5" "327" "80.00" "327"
|
||||
run_accepted_route "same-move-diagonal-chain" "Same move - diagonal chain" "320.5" "80" "320.5" "327" "80.00" "327" "315"
|
||||
|
||||
fill_box 338 79 338 347 85 342 air
|
||||
fill_box 338 80 338 347 90 342 air
|
||||
|
|
@ -321,7 +325,7 @@ run_same_move_routes() {
|
|||
fill_box 343 82 339 343 82 341 stone
|
||||
fill_box 344 83 339 344 83 341 stone
|
||||
fill_box 345 84 339 345 84 341 stone
|
||||
run_accepted_route "same-move-ascend-staircase" "Same move - ascend staircase" "340.5" "80" "340.5" "345" "85.00" "340"
|
||||
run_accepted_route "same-move-ascend-staircase" "Same move - ascend staircase" "340.5" "80" "340.5" "345" "85.00" "340" "270"
|
||||
|
||||
fill_box 360 79 358 369 85 362 air
|
||||
fill_box 360 80 358 369 90 362 air
|
||||
|
|
@ -331,7 +335,7 @@ run_same_move_routes() {
|
|||
fill_box 365 81 359 365 81 361 stone
|
||||
fill_box 366 80 359 366 80 361 stone
|
||||
fill_box 367 79 359 367 79 361 stone
|
||||
run_accepted_route "same-move-descend-staircase" "Same move - descend staircase" "362.5" "85" "360.5" "367" "80.00" "360"
|
||||
run_accepted_route "same-move-descend-staircase" "Same move - descend staircase" "362.5" "85" "360.5" "367" "80.00" "360" "270"
|
||||
|
||||
fill_box 378 79 378 390 79 382 air
|
||||
fill_box 378 80 378 390 90 382 air
|
||||
|
|
@ -340,7 +344,7 @@ run_same_move_routes() {
|
|||
set_stone 384 79 380
|
||||
set_stone 386 79 380
|
||||
set_stone 388 79 380
|
||||
run_accepted_route "same-move-aligned-parkour-chain" "Same move - aligned parkour chain" "380.5" "80" "380.5" "388" "80.00" "380"
|
||||
run_accepted_route "same-move-aligned-parkour-chain" "Same move - aligned parkour chain" "380.5" "80" "380.5" "388" "80.00" "380" "270"
|
||||
}
|
||||
|
||||
run_mixed_move_routes() {
|
||||
|
|
@ -360,7 +364,7 @@ run_mixed_move_routes() {
|
|||
set_stone 406 79 404
|
||||
set_stone 407 79 404
|
||||
set_stone 408 79 404
|
||||
run_accepted_route "mixed-traverse-turn-parkour-turn-traverse" "Mixed - traverse turn parkour turn traverse" "400.5" "80" "400.5" "408" "80.00" "404"
|
||||
run_accepted_route "mixed-traverse-turn-parkour-turn-traverse" "Mixed - traverse turn parkour turn traverse" "400.5" "80" "400.5" "408" "80.00" "404" "270"
|
||||
|
||||
fill_box 418 79 418 430 82 424 air
|
||||
fill_box 418 80 418 430 92 424 air
|
||||
|
|
@ -373,7 +377,7 @@ run_mixed_move_routes() {
|
|||
set_stone 426 81 422
|
||||
set_stone 427 80 422
|
||||
set_stone 428 79 422
|
||||
run_accepted_route "mixed-diagonal-ascend-traverse-descend" "Mixed - diagonal ascend traverse descend" "420.5" "80" "420.5" "428" "80.00" "422"
|
||||
run_accepted_route "mixed-diagonal-ascend-traverse-descend" "Mixed - diagonal ascend traverse descend" "420.5" "80" "420.5" "428" "80.00" "422" "315"
|
||||
|
||||
fill_box 438 79 438 450 82 442 air
|
||||
fill_box 438 80 438 450 92 442 air
|
||||
|
|
@ -385,7 +389,7 @@ run_mixed_move_routes() {
|
|||
set_stone 446 81 440
|
||||
set_stone 447 80 440
|
||||
set_stone 448 79 440
|
||||
run_accepted_route "mixed-traverse-ascend-parkour-descend" "Mixed - traverse ascend parkour descend" "440.5" "80" "440.5" "448" "80.00" "440"
|
||||
run_accepted_route "mixed-traverse-ascend-parkour-descend" "Mixed - traverse ascend parkour descend" "440.5" "80" "440.5" "448" "80.00" "440" "270"
|
||||
}
|
||||
|
||||
run_turn_density_routes() {
|
||||
|
|
@ -403,7 +407,7 @@ run_turn_density_routes() {
|
|||
set_stone 465 79 464
|
||||
set_stone 465 79 465
|
||||
set_stone 466 79 466
|
||||
run_accepted_route "turn-density-alternating-traverse-diagonal-chain" "Turn density - alternating traverse diagonal chain" "460.5" "80" "460.5" "466" "80.00" "466"
|
||||
run_accepted_route "turn-density-alternating-traverse-diagonal-chain" "Turn density - alternating traverse diagonal chain" "460.5" "80" "460.5" "466" "80.00" "466" "270"
|
||||
}
|
||||
|
||||
run_speed_carry_routes() {
|
||||
|
|
@ -420,7 +424,7 @@ run_speed_carry_routes() {
|
|||
set_stone 486 82 480
|
||||
set_stone 487 82 480
|
||||
set_stone 488 83 480
|
||||
run_accepted_route "speed-carry-repeated-traverse-ascend" "Speed carry - repeated traverse ascend" "480.5" "80" "480.5" "488" "84.00" "480"
|
||||
run_accepted_route "speed-carry-repeated-traverse-ascend" "Speed carry - repeated traverse ascend" "480.5" "80" "480.5" "488" "84.00" "480" "270"
|
||||
|
||||
fill_box 498 79 498 510 82 502 air
|
||||
fill_box 498 80 498 510 94 502 air
|
||||
|
|
@ -432,7 +436,7 @@ run_speed_carry_routes() {
|
|||
set_stone 505 80 500
|
||||
set_stone 506 79 500
|
||||
set_stone 507 79 500
|
||||
run_accepted_route "speed-carry-repeated-traverse-descend" "Speed carry - repeated traverse descend" "500.5" "83" "500.5" "507" "80.00" "500"
|
||||
run_accepted_route "speed-carry-repeated-traverse-descend" "Speed carry - repeated traverse descend" "500.5" "83" "500.5" "507" "80.00" "500" "270"
|
||||
|
||||
fill_box 518 79 518 532 79 522 air
|
||||
fill_box 518 80 518 532 90 522 air
|
||||
|
|
@ -443,7 +447,7 @@ run_speed_carry_routes() {
|
|||
set_stone 526 79 520
|
||||
set_stone 527 79 520
|
||||
set_stone 529 79 520
|
||||
run_accepted_route "speed-carry-repeated-traverse-parkour" "Speed carry - repeated traverse parkour" "520.5" "80" "520.5" "529" "80.00" "520"
|
||||
run_accepted_route "speed-carry-repeated-traverse-parkour" "Speed carry - repeated traverse parkour" "520.5" "80" "520.5" "529" "80.00" "520" "270"
|
||||
}
|
||||
|
||||
start_mcc
|
||||
|
|
|
|||
|
|
@ -160,11 +160,13 @@ prepare_independent_route() {
|
|||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local start_yaw="${5:-270}"
|
||||
local start_pitch="${6:-0}"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z $start_yaw $start_pitch" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +291,7 @@ run_flat_final_stop() {
|
|||
echo "== Flat final stop =="
|
||||
mc-rcon "fill 95 79 95 115 79 105 stone" >/dev/null
|
||||
mc-rcon "fill 95 80 95 115 85 105 air" >/dev/null
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5"
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5" "270"
|
||||
capture_debug_state_before_route "Flat final stop"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -315,7 +317,7 @@ run_parkour_into_turn() {
|
|||
mc-rcon "setblock 122 79 111 stone" >/dev/null
|
||||
mc-rcon "setblock 120 80 111 stone" >/dev/null
|
||||
mc-rcon "setblock 120 81 111 stone" >/dev/null
|
||||
prepare_independent_route "Parkour into L-turn" "120.5" "80" "110.5"
|
||||
prepare_independent_route "Parkour into L-turn" "120.5" "80" "110.5" "270"
|
||||
capture_debug_state_before_route "Parkour into L-turn"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -342,7 +344,7 @@ run_side_wall_jump() {
|
|||
mc-rcon "setblock 132 81 126 stone" >/dev/null
|
||||
mc-rcon "setblock 133 80 126 stone" >/dev/null
|
||||
mc-rcon "setblock 133 81 126 stone" >/dev/null
|
||||
prepare_independent_route "Rejected 2x1 side-wall jump" "131.5" "80" "127.5"
|
||||
prepare_independent_route "Rejected 2x1 side-wall jump" "131.5" "80" "127.5" "270"
|
||||
capture_debug_state_before_route "Rejected 2x1 side-wall jump"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -365,7 +367,7 @@ run_reject_3x1_gap() {
|
|||
mc-rcon "fill 140 79 135 148 79 140 stone" >/dev/null
|
||||
mc-rcon "fill 140 80 135 148 85 140 air" >/dev/null
|
||||
mc-rcon "setblock 143 80 138 stone" >/dev/null
|
||||
prepare_independent_route "Rejected 3x1 no-run-up gap" "141.5" "80" "138.5"
|
||||
prepare_independent_route "Rejected 3x1 no-run-up gap" "141.5" "80" "138.5" "270"
|
||||
capture_debug_state_before_route "Rejected 3x1 gap"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -390,7 +392,7 @@ run_corner_ascend_around_wall() {
|
|||
mc-rcon "setblock 191 80 171 stone" >/dev/null
|
||||
mc-rcon "setblock 191 80 170 stone" >/dev/null
|
||||
mc-rcon "setblock 191 81 170 stone" >/dev/null
|
||||
prepare_independent_route "Corner ascend around wall" "190.5" "80" "170.5"
|
||||
prepare_independent_route "Corner ascend around wall" "190.5" "80" "170.5" "315"
|
||||
capture_debug_state_before_route "Corner ascend around wall"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -417,7 +419,7 @@ run_wall_adjacent_descend_smoke() {
|
|||
mc-rcon "setblock 202 80 199 stone" >/dev/null
|
||||
mc-rcon "setblock 201 81 199 stone" >/dev/null
|
||||
mc-rcon "setblock 202 81 199 stone" >/dev/null
|
||||
prepare_independent_route "Wall-adjacent descend" "200.5" "81" "200.5"
|
||||
prepare_independent_route "Wall-adjacent descend" "200.5" "81" "200.5" "270"
|
||||
capture_debug_state_before_route "Wall-adjacent descend"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -441,7 +443,7 @@ run_ascend_chain_smoke() {
|
|||
mc-rcon "setblock 175 80 162 stone" >/dev/null
|
||||
mc-rcon "setblock 176 81 162 stone" >/dev/null
|
||||
mc-rcon "setblock 177 82 162 stone" >/dev/null
|
||||
prepare_independent_route "Ascend chain smoke" "171.5" "80" "160.5"
|
||||
prepare_independent_route "Ascend chain smoke" "171.5" "80" "160.5" "315"
|
||||
capture_debug_state_before_route "Ascend chain smoke"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
|
|||
|
|
@ -142,11 +142,13 @@ prepare_independent_route() {
|
|||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local start_yaw="${5:-270}"
|
||||
local start_pitch="${6:-0}"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z $start_yaw $start_pitch" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +242,7 @@ run_flat_final_stop() {
|
|||
echo "== Flat final stop =="
|
||||
mc-rcon "fill 95 79 95 115 79 105 stone" >/dev/null
|
||||
mc-rcon "fill 95 80 95 115 85 105 air" >/dev/null
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5"
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5" "270"
|
||||
capture_debug_state_before_route "Flat final stop"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
@ -263,7 +265,7 @@ run_parkour_into_turn() {
|
|||
mc-rcon "setblock 120 79 110 stone" >/dev/null
|
||||
mc-rcon "setblock 123 79 110 stone" >/dev/null
|
||||
mc-rcon "setblock 123 79 111 stone" >/dev/null
|
||||
prepare_independent_route "Parkour into turn" "120.5" "80" "110.5"
|
||||
prepare_independent_route "Parkour into turn" "120.5" "80" "110.5" "270"
|
||||
capture_debug_state_before_route "Parkour into turn"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue