pathing: align parkour contracts with live budgets

This commit is contained in:
BruceChen 2026-04-15 17:36:20 +00:00
parent ce555da95e
commit cf8bf349db
16 changed files with 3190 additions and 318 deletions

View file

@ -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() };
}

View file

@ -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()
{

View file

@ -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);

View file

@ -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));
}

View file

@ -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()
{

View file

@ -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,

View file

@ -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
}
]
}